Add JSON Inspector example app

This commit is contained in:
Tristan Penman 2020-11-08 11:30:24 +11:00
parent bbfc3f5c97
commit 8a700811d5
15 changed files with 504 additions and 0 deletions

View File

@ -175,6 +175,24 @@ The main exceptions are
Support for JSON References is in development. It is mostly working, however some of the test cases added to [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) for v6/v7 are still failing.
## JSON Inspector
An example application based on Qt is also included under [inspector](./inspector). It can be used to experiment with JSON Schemas and target documents. JSON Inspector is a self-contained CMake project, so it must be built separately:
```bash
cd inspector
mkdir build
cd build
cmake ..
make
```
Schemas and target documents can be loaded from file or entered manually. Content is parsed dynamically, so you get rapid feedback.
Here is a screenshot of JSON Inspector in action:
![JSON Inspector in action](./doc/inspector/screenshot.png)
## Documentation ##
Doxygen documentation can be built by running 'doxygen' from the project root directory. Generated documentation will be placed in 'doc/html'. Other relevant documentation such as schemas and specifications have been included in the 'doc' directory.

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

3
inspector/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
cmake-build-*/
CMakeFiles/

39
inspector/CMakeLists.txt Normal file
View File

@ -0,0 +1,39 @@
# Reference: http://qt-project.org/doc/qt-5.0/qtdoc/cmake-manual.html
cmake_minimum_required(VERSION 3.0)
# Add folder where are supportive functions
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Include Qt basic functions
include(QtCommon)
# Basic information about project
project(inspector VERSION 1.0)
# Set PROJECT_VERSION_PATCH and PROJECT_VERSION_TWEAK to 0 if not present, needed by add_project_meta
fix_project_version()
# Set additional project information
set(COPYRIGHT "Copyright (c) 2020 Tristan Penman. All rights reserved.")
set(IDENTIFIER "com.tristanpenman.valijson.inspector")
set(SOURCE_FILES
src/highlighter.cpp
src/main.cpp
src/window.cpp
)
include_directories(SYSTEM ../include)
add_project_meta(META_FILES_TO_INCLUDE)
find_package(Qt5 COMPONENTS Widgets REQUIRED)
add_executable(${PROJECT_NAME} ${OS_BUNDLE} # Expands to WIN32 or MACOS_BUNDLE depending on OS
${SOURCE_FILES} ${META_FILES_TO_INCLUDE} ${RESOURCE_FILES}
)
target_link_libraries(${PROJECT_NAME} Qt5::Widgets)

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>JSON Inspector</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,82 @@
macro(fix_project_version)
if (NOT PROJECT_VERSION_PATCH)
set(PROJECT_VERSION_PATCH 0)
endif()
if (NOT PROJECT_VERSION_TWEAK)
set(PROJECT_VERSION_TWEAK 0)
endif()
endmacro()
macro(add_project_meta FILES_TO_INCLUDE)
if (NOT RESOURCE_FOLDER)
set(RESOURCE_FOLDER res)
endif()
if (NOT ICON_NAME)
set(ICON_NAME AppIcon)
endif()
if (APPLE)
set(ICON_FILE ${RESOURCE_FOLDER}/${ICON_NAME}.icns)
elseif (WIN32)
set(ICON_FILE ${RESOURCE_FOLDER}/${ICON_NAME}.ico)
endif()
if (WIN32)
configure_file("${PROJECT_SOURCE_DIR}/cmake/windows_metafile.rc.in"
"windows_metafile.rc"
)
set(RES_FILES "windows_metafile.rc")
set(CMAKE_RC_COMPILER_INIT windres)
ENABLE_LANGUAGE(RC)
SET(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
endif()
if (APPLE)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# Identify MacOS bundle
set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME})
set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION})
set(MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION})
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
set(MACOSX_BUNDLE_COPYRIGHT ${COPYRIGHT})
set(MACOSX_BUNDLE_GUI_IDENTIFIER ${IDENTIFIER})
set(MACOSX_BUNDLE_ICON_FILE ${ICON_NAME})
endif()
if (APPLE)
set(${FILES_TO_INCLUDE} ${ICON_FILE})
elseif (WIN32)
set(${FILES_TO_INCLUDE} ${RES_FILES})
endif()
endmacro()
macro(init_os_bundle)
if (APPLE)
set(OS_BUNDLE MACOSX_BUNDLE)
elseif (WIN32)
set(OS_BUNDLE WIN32)
endif()
endmacro()
macro(fix_win_compiler)
if (MSVC)
set_target_properties(${PROJECT_NAME} PROPERTIES
WIN32_EXECUTABLE YES
LINK_FLAGS "/ENTRY:mainCRTStartup"
)
endif()
endmacro()
macro(init_qt)
# Let's do the CMake job for us
set(CMAKE_AUTOMOC ON) # For meta object compiler
set(CMAKE_AUTORCC ON) # Resource files
set(CMAKE_AUTOUIC ON) # UI files
endmacro()
init_os_bundle()
init_qt()
fix_win_compiler()

View File

@ -0,0 +1,28 @@
#include "winver.h"
IDI_ICON1 ICON DISCARDABLE "@ICON_FILE@"
VS_VERSION_INFO VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@
FILEFLAGS 0x0L
FILEFLAGSMASK 0x3fL
FILEOS 0x00040004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "CompanyName", "@COMPANY@"
VALUE "FileDescription", "@PROJECT_NAME@"
VALUE "FileVersion", "@PROJECT_VERSION@"
VALUE "LegalCopyright", "@COPYRIGHT@"
VALUE "InternalName", "@PROJECT_NAME@"
VALUE "OriginalFilename", "@PROJECT_NAME@.exe"
VALUE "ProductName", "@PROJECT_NAME@"
VALUE "ProductVersion", "@PROJECT_VERSION@"
END
END
END

4
inspector/inspector.qrc Normal file
View File

@ -0,0 +1,4 @@
<RCC version="1.0">
<qresource prefix="/">
</qresource>
</RCC>

BIN
inspector/res/AppIcon.icns Normal file

Binary file not shown.

BIN
inspector/res/AppIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

View File

@ -0,0 +1,12 @@
#include "highlighter.h"
Highlighter::Highlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
// TODO
}
void Highlighter::highlightBlock(const QString &text)
{
// TODO
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <QSyntaxHighlighter>
class Highlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
Highlighter(QTextDocument * parent = 0);
protected:
void highlightBlock(const QString & text) override;
};

12
inspector/src/main.cpp Normal file
View File

@ -0,0 +1,12 @@
#include <QApplication>
#include "window.h"
int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}

206
inspector/src/window.cpp Normal file
View File

@ -0,0 +1,206 @@
#include <sstream>
#include <stdexcept>
#include <QFile>
#include <QFileDialog>
#include <QMenu>
#include <QSplitter>
#include <QStatusBar>
#include <QString>
#include <QTabWidget>
#include <QTextEdit>
#include <QToolBar>
#include <QToolButton>
#include <valijson/adapters/qtjson_adapter.hpp>
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validation_results.hpp>
#include <valijson/validator.hpp>
#include "highlighter.h"
#include "window.h"
Window::Window(QWidget * parent)
: QMainWindow(parent)
, m_schema(nullptr)
{
setWindowTitle("JSON Inspector");
m_documentEditor = createEditor(false);
m_schemaEditor = createEditor(false);
m_errors = createEditor(true);
auto documentTabWidget = createTabWidget(m_documentEditor, "Document");
auto schemaTabWidget = createTabWidget(m_schemaEditor, "Schema");
auto horizontalSplitter = createSplitter(schemaTabWidget, documentTabWidget, true);
auto errorsTabWidget = createTabWidget(m_errors, "Errors");
auto verticalSplitter = createSplitter(horizontalSplitter, errorsTabWidget, false);
verticalSplitter->setStretchFactor(0, 2);
verticalSplitter->setStretchFactor(1, 1);
auto toolBar = createToolBar();
auto statusBar = createStatusBar();
addToolBar(toolBar);
setCentralWidget(verticalSplitter);
setStatusBar(statusBar);
connect(m_documentEditor, SIGNAL(textChanged()), this, SLOT(refreshDocumentJson()));
connect(m_schemaEditor, SIGNAL(textChanged()), this, SLOT(refreshSchemaJson()));
}
QTextEdit * Window::createEditor(bool readOnly)
{
QFont font;
font.setFamily("Courier");
font.setFixedPitch(true);
font.setPointSize(12);
auto editor = new QTextEdit();
editor->setFont(font);
editor->setReadOnly(readOnly);
auto highlighter = new Highlighter(editor->document());
return editor;
}
QSplitter * Window::createSplitter(QWidget * left, QWidget * right, bool horizontal)
{
auto splitter = new QSplitter(horizontal ? Qt::Horizontal : Qt::Vertical);
splitter->setChildrenCollapsible(false);
splitter->insertWidget(0, left);
splitter->insertWidget(1, right);
return splitter;
}
QStatusBar * Window::createStatusBar()
{
return new QStatusBar();
}
QTabWidget * Window::createTabWidget(QWidget * child, const QString & name)
{
auto tabWidget = new QTabWidget();
tabWidget->addTab(child, name);
tabWidget->setDocumentMode(true);
return tabWidget;
}
QToolBar * Window::createToolBar()
{
auto toolbar = new QToolBar();
toolbar->setMovable(false);
auto openMenu = new QMenu("Open");
auto openSchemaAction = openMenu->addAction("Open Schema...");
auto openDocumentAction = openMenu->addAction("Open Document...");
auto openButton = new QToolButton();
openButton->setMenu(openMenu);
openButton->setPopupMode(QToolButton::MenuButtonPopup);
openButton->setText("Open");
openButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
toolbar->addWidget(openButton);
connect(openButton, &QToolButton::clicked, openButton, &QToolButton::showMenu);
connect(openDocumentAction, SIGNAL(triggered()), this, SLOT(showOpenDocumentDialog()));
connect(openSchemaAction, SIGNAL(triggered()), this, SLOT(showOpenSchemaDialog()));
return toolbar;
}
void Window::refreshDocumentJson()
{
QJsonParseError error;
m_document = QJsonDocument::fromJson(m_documentEditor->toPlainText().toUtf8(), &error);
if (m_document.isNull()) {
m_errors->setText(error.errorString());
return;
}
if (m_schema) {
validate();
} else {
m_errors->setText("");
}
}
void Window::refreshSchemaJson()
{
QJsonParseError error;
auto schemaDoc = QJsonDocument::fromJson(m_schemaEditor->toPlainText().toUtf8(), &error);
if (schemaDoc.isNull()) {
m_errors->setText(error.errorString());
return;
}
try {
valijson::adapters::QtJsonAdapter adapter(schemaDoc.object());
valijson::SchemaParser parser;
delete m_schema;
m_schema = new valijson::Schema();
parser.populateSchema(adapter, *m_schema);
m_errors->setText("");
} catch (std::runtime_error error) {
delete m_schema;
m_schema = nullptr;
m_errors->setText(error.what());
}
validate();
}
void Window::showOpenDocumentDialog()
{
const QString fileName = QFileDialog::getOpenFileName(this, "Open Document", QString(), QString("*.json"));
if (!fileName.isEmpty()) {
QFile file(fileName);
file.open(QFile::ReadOnly | QFile::Text);
m_documentEditor->setText(file.readAll());
}
}
void Window::showOpenSchemaDialog()
{
const QString fileName = QFileDialog::getOpenFileName(this, "Open Schema", QString(), QString("*.json"));
if (!fileName.isEmpty()) {
QFile file(fileName);
file.open(QFile::ReadOnly | QFile::Text);
m_schemaEditor->setText(file.readAll());
}
}
void Window::validate()
{
valijson::ValidationResults results;
valijson::Validator validator;
valijson::adapters::QtJsonAdapter adapter(m_document.object());
if (validator.validate(*m_schema, adapter, &results)) {
m_errors->setText("Document is valid.");
return;
}
valijson::ValidationResults::Error error;
unsigned int errorNum = 1;
std::stringstream ss;
while (results.popError(error)) {
std::string context;
for (auto itr = error.context.begin(); itr != error.context.end(); itr++) {
context += *itr;
}
ss << "Error #" << errorNum << std::endl
<< " context: " << context << std::endl
<< " desc: " << error.description << std::endl;
++errorNum;
}
m_errors->setText(QString::fromStdString(ss.str()));
}

48
inspector/src/window.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <QJsonDocument>
#include <QMainWindow>
class QJsonDocument;
class QSplitter;
class QStatusBar;
class QTabWidget;
class QTextEdit;
class QToolBar;
namespace valijson {
class Schema;
}
class Window : public QMainWindow
{
Q_OBJECT
public:
Window(QWidget * parent = 0);
public slots:
void refreshDocumentJson();
void refreshSchemaJson();
void showOpenDocumentDialog();
void showOpenSchemaDialog();
private:
QTextEdit * createEditor(bool readOnly);
QSplitter * createSplitter(QWidget * left, QWidget * right, bool horizontal);
QStatusBar * createStatusBar();
QTabWidget * createTabWidget(QWidget * child, const QString & name);
QToolBar * createToolBar();
void validate();
QTextEdit * m_documentEditor;
QTextEdit * m_schemaEditor;
QTextEdit * m_errors;
QJsonDocument m_document;
valijson::Schema * m_schema;
};