mirror of
https://github.com/tristanpenman/valijson.git
synced 2025-03-03 12:58:03 +01:00
Add JSON Inspector example app
This commit is contained in:
parent
bbfc3f5c97
commit
8a700811d5
18
README.md
18
README.md
@ -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:
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
BIN
doc/inspector/screenshot.png
Normal file
BIN
doc/inspector/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 116 KiB |
3
inspector/.gitignore
vendored
Normal file
3
inspector/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
cmake-build-*/
|
||||
CMakeFiles/
|
39
inspector/CMakeLists.txt
Normal file
39
inspector/CMakeLists.txt
Normal 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)
|
38
inspector/cmake/MacOSXBundleInfo.plist.in
Normal file
38
inspector/cmake/MacOSXBundleInfo.plist.in
Normal 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>
|
82
inspector/cmake/QtCommon.cmake
Normal file
82
inspector/cmake/QtCommon.cmake
Normal 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()
|
28
inspector/cmake/windows_metafile.rc.in
Normal file
28
inspector/cmake/windows_metafile.rc.in
Normal 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
4
inspector/inspector.qrc
Normal file
@ -0,0 +1,4 @@
|
||||
<RCC version="1.0">
|
||||
<qresource prefix="/">
|
||||
</qresource>
|
||||
</RCC>
|
BIN
inspector/res/AppIcon.icns
Normal file
BIN
inspector/res/AppIcon.icns
Normal file
Binary file not shown.
BIN
inspector/res/AppIcon.ico
Normal file
BIN
inspector/res/AppIcon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 550 KiB |
12
inspector/src/highlighter.cpp
Normal file
12
inspector/src/highlighter.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "highlighter.h"
|
||||
|
||||
Highlighter::Highlighter(QTextDocument *parent)
|
||||
: QSyntaxHighlighter(parent)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void Highlighter::highlightBlock(const QString &text)
|
||||
{
|
||||
// TODO
|
||||
}
|
14
inspector/src/highlighter.h
Normal file
14
inspector/src/highlighter.h
Normal 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
12
inspector/src/main.cpp
Normal 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
206
inspector/src/window.cpp
Normal 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
48
inspector/src/window.h
Normal 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;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user