/******************************************************************************** * * * J S O N R e a d e r & W r i t e r * * * ********************************************************************************* * Copyright (C) 2013,2024 by Jeroen van der Zijp. All Rights Reserved. * ********************************************************************************* * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this program. If not, see * ********************************************************************************/ #ifndef FXJSON_H #define FXJSON_H #ifndef FXPARSEBUFFER_H #include "FXParseBuffer.h" #endif namespace FX { /** * The FXJSON serializer loads or saves an FXVariant to a JSON text file. * Since FXVariant can contain an arbitrarily complex data structure, this * provides applications with a convenient way to load and save state information * in a well-defined and human-readable file format. * The base class implements serialization/deserialization to/from an external * buffer. * * Subclasses FXJSONFile and FXJSONString serialize from/to strings and disk- * based files, respectively. * The new JSON5 standard may also be parsed, allowing for single- and multi-line * nested comments to be embedded in the input, and makes quotation of variable * names optional. In addition, JSON5 also allows use of single quotes (') as * well as double quotes ("). * * Syntax errors in the input cause the parser to return an error, and allow * diagnosis of the problem and its location in the file by line number, column * number, and byte-offset from the start of the file. * * When writing a json stream, the generated output may be formatted in different * ways. The flow-mode controls the overall layout of the resulting text output; * when flow is set to Stream, all output is generated with no formatting to * improve human legibility. This is the most space-friendly format possible. * If flow is set to Compact, a human readable, compact format, aiming to * maximize the amount of information on each line is generated. * When flow is set to Pretty, a nicely indented, but extremely "airy" output * results, and the resulting document will contain many, many lines with * little data. * * Numeric values are printed with configurable precision; (default=15 digits * which results in minimal information loss for real numbers). * For Pretty flow format, output may be indented in multiples of the indent * level (default=2). Depending on flow setting, lines may be wrapped at a * maximum number of columns (default=80). * Output strings containing reserved characters may have to be escaped. * For UTF8 characters, there are 3 options for escaping. * * - Escape mode 0: UTF8 characters are passed unescaped. * - Escape mode 1: UTF8 characters are escaped as \xXX. * - Escape mode 2: UTF8 will be escaed using Unicode escape sequences of * the for \uXXXX or \uXXXX\uXXXX (two surrogate-pairs escape codes * for code points exceeding 16 bits). * * The default setting is to allow UTF8 characters in the output, but be aware * that such outputs need UTF8-capable viewer software to be rendered properly. * Finally, in JSON5 mode (version set to 5), variable names may be written as * unquoted strings if their syntax allows for it; in JSON5 mode, single quotes * may be selected to improve human legibility. */ class FXAPI FXJSON : public FXParseBuffer { public: /// JSON deserializer error codes enum Error { ErrOK, /// No errors ErrSave, /// Unable to save ErrLoad, /// Unable to load ErrToken, /// Illegal token ErrColon, /// Expected colon ':' ErrComma, /// Expected comma ',' ErrBracket, /// Expected closing bracket ErrBrace, /// Expected closing brace ErrQuotes, /// Expected closing quotes ErrQuote, /// Expected closing quote ErrNumber, /// Numeric conversion ErrIdent, /// Unexpected identifier ErrDuplicate, /// Duplicate key encountered ErrEnd /// Unexpected end of file }; /// JSON serializer flow modes enum Flow { Stream, /// Stream-of-consciousness output Compact, /// Compact, human readable output (default) Pretty /// Pretty printed, indented output }; protected: enum Token { TK_ERROR, TK_EOF, TK_COMMA, TK_COLON, TK_IDENT, TK_NAN, TK_INF, TK_NULL, TK_FALSE, TK_TRUE, TK_STRING, TK_PLUS, TK_MINUS, TK_INT, TK_HEX, TK_REAL, TK_LBRACK, TK_LBRACE, TK_RBRACK, TK_RBRACE }; protected: FXString value; // Token value FXlong offset; // Position from start Token token; // Token FXint column; // Column number FXint indent; // Indent level FXint line; // Line number FXint wrap; // Line wrap column FXchar quote; // Quote type used FXuchar flow; // Output flow FXuchar prec; // Float precision FXuchar fmt; // Float format FXuchar esc; // Escape mode FXuchar dent; // Indentation amount FXuchar ver; // Version protected: static const FXchar *const errors[]; protected: virtual Token next(); Token ident(); Token string(); static Token identoken(const FXString& str); Error loadMap(FXVariant& var); Error loadArray(FXVariant& var); Error loadVariant(FXVariant& var); Error saveString(const FXString& str); Error saveIdent(const FXString& str); Error saveMap(const FXVariant& var); Error saveArray(const FXVariant& var); Error saveVariant(const FXVariant& var); private: FXJSON(const FXJSON&); FXJSON &operator=(const FXJSON&); public: /** * Initialize JSON serializer. */ FXJSON(); /** * Initialize JSON serializer with buffer of size and direction. * Text location (column, line number, byte offset) is reset. */ FXJSON(FXchar* buffer,FXuval sz=8192,Direction d=Load); /** * Open JSON parse buffer with given size and direction. * Text location (column, line number, byte offset) is reset. */ FXbool open(FXchar* buffer=nullptr,FXuval sz=8192,Direction d=Load); /** * Return current line number. */ FXint getLine() const { return line; } /** * Return current column number. */ FXint getColumn() const { return column; } /** * Return offset from begin of file. */ FXlong getOffset() const { return offset; } /** * Load a variant from JSON stream. * Return false if stream wasn't opened for loading, or syntax error. */ virtual Error load(FXVariant& variant); /** * Save a variant to JSON stream. * Return false if stream wasn't opened for saving, or disk was full. */ virtual Error save(const FXVariant& variant); /** * Returns error for given error code. */ static const FXchar* getError(Error err){ return errors[err]; } /** * Floating point output precision control. * This controls the number of significant digits written to * the output. The default is 15. */ void setNumericPrecision(FXuchar p){ prec=p; } FXuchar getNumericPrecision() const { return prec; } /** * Floating point output format control. * The format mode is interpreted as follows: * * 0 No exponent. * 1 Exponent. * 2 Output exponent when required. * * The default mode is 2. */ void setNumericFormat(FXuchar f){ fmt=f; } FXuchar getNumericFormat() const { return fmt; } /** * Change output flow format (Stream, Compact, Pretty). * Stream is the most compact, but pretty much unreadable by humans; it * aims to be compact. * Compact is very human-readable while at the same time using minimum number * of lines to represent the output. * Pretty will print one item on each line, with indentation. It is very easily * readable but produces large numbers of text lines. */ void setOutputFlow(FXuchar f){ flow=f; } FXuchar getOutputFlow() const { return flow; } /** * Change indentation level for pretty print flow, the amount of * indentation applied for each level. */ void setIndentation(FXuchar d){ dent=d; } FXuchar getIndentation() const { return dent; } /** * Change column at which lines are wrapped. */ void setLineWrap(FXint w){ wrap=w; } FXint getLineWrap() const { return wrap; } /** * Change string escape mode. * The escape mode is interpreted as follows: * * 0 Don't escape unicode in strings; * 1 Escape unicode as \xXX * 2 Escape unicode as \uXXXX or \uXXXX\uXXXX. * * Default is to escape control characters only. */ void setEscapeMode(FXuchar e){ esc=e; } FXuchar getEscapeMode() const { return esc; } /** * Change json version. */ void setVersion(FXuchar v){ ver=v; } FXuchar getVersion() const { return ver; } /** * Change quote type, either (') or ("). */ void setQuote(FXchar q){ quote=q; } FXchar getQuote() const { return quote; } /** * Close stream and delete buffer, if owned. * To permit diagnostics, text location not reset. */ FXbool close(); /** * Close JSON stream and clean up. */ virtual ~FXJSON(); }; } #endif