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. 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 ## ## 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. 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;
};