Giter VIP home page Giter VIP logo

json5pp's Introduction

json5pp

JSON (ECMA-404 and JSON5) parser & stringifier written in C++11.

Features

  • Easy to embed to your project. Only single file json5pp.hpp required.
  • Parse standard JSON (ECMA-404) from std::istream or std::string.
  • Parse JSON5 from std::istream or std::string.
  • Stringify to std::ostream or std::string as standard JSON.
  • Stringify to std::ostream or std::string as JSON5.

Requirements

  • Compilers with C++11 support

License

  • MIT license

API

JSON value type

namespace json5pp {
  class value {
  };
}
  • The class holds a value which is represented in JSON.
  • This class can hold all types in JSON:
    • null
    • boolean (true / false)
    • number
    • string
    • array (stored as std::vector<json5pp::value>)
    • object (stored as std::map<std::string, json5pp::value>)
  • Provides type check with is_xxx() method. (xxx is one of null, boolean, number, string, array and object)
  • Provides explicit cast to C++ type with as_xxx() method. (xxx is one of null, boolean, number, integer, string, array and object)
    • If cast failed, throws std::bad_cast
  • Accepts implicit cast (by overload of operator=) from C++ type (nullptr_t, bool, double | int, std::string | const char*)
  • See examples for details

Parse functions

namespace json5pp {
  value parse(const std::string& str);
}
  • Parse given string.
  • String must be a valid JSON (ECMA-404 standard)
    • If not valid, throws json5pp::syntax_error.
  • String must be a finished (closed) JSON.
    • i.e. If there are any junk data (except for spaces) after JSON, throws json5pp::syntax_error.
namespace json5pp {
  value parse(std::istream& istream, bool finish = true)
}
  • Parse JSON from given input stream.
  • Stream must have a valid JSON (ECMA-404 standard).
    • If not valid, throws json5pp::syntax_error.
  • If finish is true, stream must be closed by eof after JSON.
namespace json5pp {
  value parse5(const std::string& str);
}
  • JSON5 version of parse(const std::string&)
namespace json5pp {
  value parse5(std::istream& istream, bool finish = true);
}
  • JSON5 version of parse(std::istream&, bool)

Stringify functions

namespace json5pp {
  class value {
    template <class... T>
    std::string stringify(T... manip);
  };
}
namespace json5pp {
  class value {
    template <class... T>
    std::string stringify5(T... manip);
  };
}
namespace json5pp {
  template <class... T>
  std::string stringify(const value& v, T... manip);
}
  • Global method version of json5pp::value::stringify()
  • Same as v.stringify(manip...)
namespace json5pp {
  template <class... T>
  std::string stringify5(const value& v, T... manip);
}
  • Global method version of json5pp::value::stringify5()
  • Same as v.stringify5(manip...)

iostream API

Parse by operator>>

std::istream& istream = ...;
json5pp::value v;
istream >> v;

Parse by operator>> with manipulators

std::istream& istream = ...;
json5pp::value v;
istream >> json5pp::rule::json5() >> v; // Parse as JSON5

Stringify by operator<<

std::ostream& ostream = ...;
const json5pp::value& v = ...;
ostream << v;

Stringify by operator<< with manipulators

std::ostream& ostream = ...;
const json5pp::value& v = ...;
ostream << json5pp::rule::tab_indent<1>() << v;   // Stringify with tab indent
ostream << json5pp::rule::space_indent<2>() << v; // Stringify with 2-space indent

Manipulators

Comments

  • json5pp::rule::single_line_comment()
  • json5pp::rule::no_single_line_comment()
    • Allow/disallow single line comment starts with //
  • json5pp::rule::multi_line_comment()
  • json5pp::rule::no_multi_line_comment()
    • Allow/disallow multiple line comment starts with /* and ends with */
  • json5pp::rule::comments()
  • json5pp::rule::no_comments()
    • Combination of single_line_comment and multi_line_comment

Numbers

  • json5pp::rule::explicit_plus_sign()
  • json5pp::rule::no_explicit_plus_sign()
    • Allow/disallow explicit plus sign (+) before non-negative number (ex: +123)
  • json5pp::rule::leading_decimal_point()
  • json5pp::rule::no_leading_decimal_point()
    • Allow/disallow leading decimal point before number (ex: .123)
  • json5pp::rule::trailing_decimal_point()
  • json5pp::rule::no_trailing_decimal_point()
    • Allow/disallow trailing decimal point after number (ex: 123.)
  • json5pp::rule::decimal_points()
  • json5pp::rule::no_decimal_points()
    • Combination of leading_decimal_point and trailing_decimal_point
  • json5pp::rule::infinity_number()
  • json5pp::rule::no_infinity_number()
    • Allow/disallow infinity number
  • json5pp::rule::not_a_number()
  • json5pp::rule::no_not_a_number()
    • Allow/disallow NaN
  • json5pp::rule::hexadecimal()
  • json5pp::rule::no_hexadecimal()
    • Allow/disallow hexadecimal number (ex: 0x123)

Strings

  • json5pp::rule::single_quote()
  • json5pp::rule::no_single_quote()
    • Allow/disallow single quoted string (ex: 'foobar')
  • json5pp::rule::multi_line_string()
  • json5pp::rule::no_multi_line_string()
    • Allow/disallow multiple line string escaped by \
    • Example:
      "test\
      2nd line"

Arrays and objects

  • json5pp::rule::trailing_comma()
  • json5pp::rule::no_trailing_comma()
    • Allow/disallow trailing comma at the end of arrays or objects.
    • Example for arrays: [1,2,3,]
    • Example for objects: {"a":123,}

Objects

  • json5pp::rule::unquoted_key()
  • json5pp::rule::no_unquoted_key()
    • Allow/disallow unquoted keys in objects. (ex: {a:123})

Rule sets

  • json5pp::rule::ecma404()
    • ECMA-404 standard rule set.
  • json5pp::rule::json5()
    • JSON5 rule set.

Parse options

  • json5pp::rule::finished()
    • Parse as finished (closed) JSON. If any junk data follows after JSON, parse fails.
    • Opposite to json5pp::rule::streaming()
  • json5pp::rule::streaming()
    • Parse as non-finished (non-closed) JSON. Parse will succeed at the end of JSON.
    • Opposite to json5pp::rule::finished()

Stringify options

  • json5pp::rule::lf_newline()
    • When indent is enabled, use LF(\n) as new-line code.
    • Opposite to json5pp::rule::crlf_newline
  • json5pp::rule::crlf_newline()
    • When indent is enabled, use CR+LF(\r\n) as new-line code.
    • Opposite to json5pp::rule::lf_newline
  • json5pp::rule::no_indent()
    • Disable indent. All arrays and objects will be stringified as one-line.
  • json5pp::rule::tab_indent<L>()
    • Enable indent with tab character(s).
    • L means a number of tab (\t) characters for one-level indent.
    • If L is omitted, treat as L=1.
  • json5pp::rule::space_indent<L>()
    • Enable indent with space character(s).
    • L means a number of space ( ) characters for one-level indent.
    • If L is omitted, treat as L=2.

Examples

Constructing value object

using namespace std;

// Construct "null" value
json5pp::value a;               // Default constructor
cout << a.is_null() << endl;    // => 1
cout << a << endl;              // => null

json5pp::value b(nullptr);      // Constructor with std::nullptr_t argument
cout << b.is_null() << endl;    // => 1
cout << b << endl;              // => null

// json5pp::value c(NULL);      // Compile error
                                // NULL cannot be used instead of nullptr

// Construct boolean value
json5pp::value d(true);         // Constructor with bool argument
cout << d.is_boolean() << endl; // => 1
cout << d << endl;              // => true

// Construct number value
json5pp::value e(123.45);       // Constructor with double argument
cout << e.is_number() << endl;  // => 1
cout << e << endl;              // => 123.45

json5pp::value f(789);          // Constructor with int argument
cout << f.is_number() << endl;  // => 1
cout << f << endl;              // => 789

// Construct string value
std::string str("foo");
json5pp::value g(str);          // Constructor with const std::string& argument
cout << g.is_string() << endl;  // => 1
cout << g << endl;              // => "foo"

json5pp::value h("bar");        // Constructor with const char* argument
cout << h.is_string() << endl;  // => 1
cout << h << endl;              // => "bar"

// Construct array value
json5pp::value i {1, false, "baz", nullptr};
                                // Construct with std::initializer_list
cout << i.is_array() << endl;   // => 1
cout << i << endl;              // => [1,false,"baz",null]

auto j = json5pp::array({1, false, "baz", nullptr});
                                // Construct with utility function: json5pp::array()
cout << j.is_array() << endl;   // => 1
cout << j << endl;              // => [1,false,"baz",null]

// json5pp::value k {{"foo", 123}};
                                // Compile error (*1)

// Construct object value
auto m = json5pp::object({{"bar", 123}, {"foo", "baz"}});
                                // Construct with utility function: json5pp::object()
cout << m.is_object() << endl;  // => 1
cout << m << endl;              // => {"bar":123,"foo":"baz"}

// json5pp::value n{{"foo", 123}};
                                // Compile error (*1)

// (*1)
// These forms are rejected because an implicit conversion
// for arrays and objects makes ambiguousness, for example:
// json5pp::value x{{"foo", 123}};  // Ambiguous!
//                                  // candidate: [["foo",123]]
//                                  // candidate: {"foo":123}
//
// Use utility functions (json5pp::array(),json5pp::object()) to remove
// ambiguousness.

Changing value object

using namespace std;

json5pp::value x;       // Default constructor makes null value
cout << x << endl;      // => null
x = false;              // Assign boolean with bool value
cout << x << endl;      // => false
x = 123.45;             // Assign number with double value
cout << x << endl;      // => 123.45
x = 789;                // Assign number with int value
cout << x << endl;      // => 789
std::string str("bar");
x = str;                // Assign string with const std::string& value
cout << x << endl;      // => "bar"
                        // Because assignment copies the content of string,
                        // changing a source string does not affect `x`:
str += "123";
cout << x << endl;      // => "bar" (Contents does NOT change)
x = "foo";              // Assign string with const char* value
cout << x << endl;      // => "foo"
x = nullptr;            // Assign null with nullptr_t value
cout << x << endl;      // => null

x = json5pp::array({1});// Assign array with utility function: json5pp::array()
cout << x << endl;      // => [1]

auto& a = x.as_array(); // You can get container object by as_array() method
                        // decltype(a) => std::vector<json5pp::value>&
a.push_back("foo");     // Add value at the end of array
cout << x << endl;      // => [1,"foo"]

x = json5pp::object({{"foo","bar"}});
                        // Assign object with utility function: json5pp::object()
cout << x << endl;      // => {"foo":"bar"}

// Note: Do not access `a` (array container) after replacing the content of `x`
//       with a new object.

auto& o = x.as_object();// You can get container object by as_object() method
                        // decltype(o) => std::map<std::string, json5pp::value>&
                        // Note: Do not forget `&` when you use `auto` keyword!
o.emplace("baz", 123);  // Add value with key "baz"
cout << x << endl;      // => {"baz":123,"foo":"bar"}

json5pp::value y;
y = x;                  // You can copy the value by "=" operator
cout << y << endl;      // => {"baz":123,"foo":"bar"}

                        // Because assignment arrays/objects is a deep copy,
                        // changing a source value `x` does not affect `y`:
o.erase("foo");         // Remove key "foo" from the content of `x` object
cout << x << endl;      // => {"baz":123}
cout << y << endl;      // => {"baz":123,"foo":"bar"} (Contents does NOT change)

Accessing value object

using namespace std;

// Access boolean (See also: Truthy/falsy tests)
json5pp::value a(true);
auto a_value = a.as_boolean();    // decltype(a_value) => bool
cout << a_value << endl;          // => 1

// Access number
json5pp::value b(123.45);
auto b_value1 = b.as_number();    // decltype(b_value1) => double
cout << b_value1 << endl;         // => 123.45
auto b_value2 = b.as_integer();   // decltype(b_value2) => int
cout << b_value2 << endl;         // => 123

// Access string
json5pp::value c("foo");
auto c_value = c.as_string();     // decltype(c_value) => std::string
cout << c_value << endl;          // => foo

// Access array
json5pp::value d{1, "foo", false};
auto& d_value = d.as_array();     // decltype(d_value) => std::vector<json5pp::value>&
cout << d_value.size() << endl;           // => 3
cout << d_value[0].as_number() << endl;   // => 1
cout << d_value[1].as_string() << endl;   // => foo
cout << d_value[2].as_boolean() << endl;  // => 0

// Access array with indexer
cout << d[0].as_number() << endl;         // => 1
cout << d[1].as_string() << endl;         // => foo
cout << d[2].as_boolean() << endl;        // => 0
// d[1] = 123;                            // Compile error (indexer is read-only)

// Access object
auto e = json5pp::object({{"bar", 123}, {"foo", true}});
auto& e_value = e.as_object();    // decltype(e_value) => std::map<std::string, json5pp::value>&
cout << e_value.size() << endl;                 // => 2
cout << e_value.at("bar").as_number() << endl;  // => 123
cout << e_value.at("foo").as_boolean() << endl; // => 1

// Access object with indexer
cout << e["bar"].as_number() << endl;     // => 123
cout << e["foo"].as_boolean() << endl;    // => 1
// e["baz"] = 123;                        // Compile error (indexer is read-only)

// Invalid cast
// (If type does not match, as_xxx() method throws std::bad_cast())
json5pp::value f(123);    // type is number
// f.as_null();           // throws std::bad_cast()
json5pp::value g();       // type is null
// f.as_boolean();        // throws std::bad_cast()

// Truthy/falsy test
// falsy: null, false, 0, -0, NaN, ""
// truthy: other values
json5pp::value truthy1(true);
json5pp::value truthy2(1);
json5pp::value truthy3("foo");
json5pp::value truthy4{};
auto truthy5 = json5pp::object({});
cout << (bool)truthy1 << endl;  // => 1
cout << (bool)truthy2 << endl;  // => 1
cout << (bool)truthy3 << endl;  // => 1
cout << (bool)truthy4 << endl;  // => 1
cout << (bool)truthy5 << endl;  // => 1

json5pp::value falsy1();        // null
json5pp::value falsy2(false);
json5pp::value falsy3(0);
json5pp::value falsy4(numeric_limits<double>::quiet_NaN());     // NaN
json5pp::value falsy5(numeric_limits<double>::signaling_NaN()); // NaN
json5pp::value falsy6("");
cout << (bool)falsy1 << endl;  // => 0
cout << (bool)falsy2 << endl;  // => 0
cout << (bool)falsy3 << endl;  // => 0
cout << (bool)falsy4 << endl;  // => 0
cout << (bool)falsy5 << endl;  // => 0
cout << (bool)falsy6 << endl;  // => 0

Parse

using namespace std;

auto x = json5pp::parse("{\"foo\":[123,\"baz\"]}");
cout << x.is_object() << endl;            // => 1
cout << x["foo"].is_array() << endl;      // => 1
cout << x["foo"][0].as_number() << endl;  // => 123
cout << x["foo"][1].as_string() << endl;  // => baz

auto y = json5pp::parse5("{\"foo\"://this is comment\n[123,\"baz\"/*trailing comma-->*/,],}");
cout << y.is_object() << endl;            // => 1
cout << y["foo"].is_array() << endl;      // => 1
cout << y["foo"][0].as_number() << endl;  // => 123
cout << y["foo"][1].as_string() << endl;  // => baz

Stringify

using namespace std;

// Make some example object...
json5pp::value x = json5pp::object({
  {"foo", 123},
  {"bar",
    json5pp::array({
      1, "baz", true,
    })
  },
});

// Stringify to output stream by "<<" operator
cout << x << endl;

// Stringify to std::string by stringify() method
auto s = x.stringify(); // decltype(s) => std::string
cout << s << endl;      // => {"bar":[1,"baz",true],"foo":123}

// Stringify to output stream with indent specification
cout << json5pp::rule::space_indent<>() << x << endl; /* =>
{
  "bar": [
    1,
    "baz",
    true
  ],
  "foo": 123
}
*/

// Stringify to std::string with indent specification
auto s2 = x.stringify(json5pp::rule::tab_indent<>());
cout << s2 << endl; /* =>
{
>       "bar": [
>       >       1,
>       >       "baz",
>       >       true,
>       ],
>       "foo": 123
}
(`>` means tab) */

Limitation

  • Not fully compatible with unquoted keys in JSON5 (Some unicode will be rejected as keys)
  • All strings are assumed to be stored in UTF-8 encoding.

ToDo

  • More tests

json5pp's People

Contributors

kimushu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

json5pp's Issues

Some recent warnings I got when compiling with -Wall -Wextra

With the latest commit I observed the following warnings when compiling code with -Wall -Wextra, which is the default warning level I currently use on my projects.

  1. Some null-related warnings with json5pp::value's constructor:
json5pp/json5pp.hpp: In constructor ‘json5pp::value::value(json5pp::value::null_type)’:
json5pp/json5pp.hpp:162:19: warning: unused parameter ‘null’ [-Wunused-parameter]
  162 |   value(null_type null) noexcept : type(TYPE_NULL) {}
      |         ~~~~~~~~~~^~~~
json5pp/json5pp.hpp: In member function ‘json5pp::value& json5pp::value::operator=(json5pp::value::null_type)’:
json5pp/json5pp.hpp:594:30: warning: unused parameter ‘null’ [-Wunused-parameter]
  594 |   value& operator=(null_type null)
      |                    ~~~~~~~~~~^~~~
  1. json5pp::rule::json5() (I use this to read JSON5 from std::ifstream to a json5pp::value) causes the following notes and warnings:
json5pp/json5pp.hpp:789:65: warning: unused parameter ‘manip’ [-Wunused-parameter]
  789 |   parser<((F&~C)|S)&M> operator>>(const manipulator_flags<S,C>& manip)
      |                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
json5pp/json5pp.hpp: In member function ‘int json5pp::impl::parser<F>::skip_spaces() [with unsigned int F = 4095]’:
json5pp/json5pp.hpp:873:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
  873 |         if (has_flag(flags::single_line_comment | flags::multi_line_comment)) {
      |         ^~
json5pp/json5pp.hpp:908:7: note: here
  908 |       default:
      |       ^~~~~~~
json5pp/json5pp.hpp: In member function ‘void json5pp::impl::parser<F>::parse_string(std::string&, int, const char*) [with unsigned int F = 4095]’:
json5pp/json5pp.hpp:1286:11: warning: this statement may fall through [-Wimplicit-fallthrough=]
 1286 |           if (has_flag(flags::multi_line_string)) {
      |           ^~
json5pp/json5pp.hpp:1294:9: note: here
 1294 |         case '\n':
      |         ^~~~
json5pp/json5pp.hpp:1295:11: warning: this statement may fall through [-Wimplicit-fallthrough=]
 1295 |           if (has_flag(flags::multi_line_string)) {
      |           ^~
json5pp/json5pp.hpp:1299:9: note: here
 1299 |         default:
      |         ^~~~~~~
json5pp/json5pp.hpp: In member function ‘void json5pp::impl::parser<F>::parse_number(json5pp::value&, int) [with unsigned int F = 4095]’:
json5pp/json5pp.hpp:1173:22: warning: this statement may fall through [-Wimplicit-fallthrough=]
 1173 |         exp_negative = true;
      |         ~~~~~~~~~~~~~^~~~~~
json5pp/json5pp.hpp:1175:7: note: here
 1175 |       case '+':
      |       ^~~~

This is on gcc/g++ 10.2.0. I haven't noticed any major issues stemmed from those warnings yet, and as I currently have only a few places actually using json5pp::value so I'm currently not getting too many warnings from it. For now I'll suppress the warnings via -isystem to better focus on warnings coming from my own code in case there are any.

EDIT: I noticed some /* no-break */ comments. I'm not certain which compiler/editor respects this comment but g++ actually doesn't. Using // fall through suppressed the implicit fallthrough warnings for g++ according to this. Still I think a portable solution might be needed.

EDIT 2: I just found this method which works to suppress the unused parameter warnings for gcc/g++, as at one time I was dealing with a similar situation. Adding __attribute__((unused)) after the parameter that would not be used in the scope suppressed the warnings in my case. However, like the previous edit, this is probably specific to gcc/g++ and different workarounds might be needed for other compilers.

EDIT 3: Okay... it appeared I used a different method for the unused parameter warnings for the null_type value, by removing the parameter name from the prototype and declaration (leaving only the type).

Issues with int64_t (and related) type due to ambiguity

It seems json5pp can't be used with int64_t (long in Linux, long long in Windows) by default, generating this error:

error: conversion from ‘int64_t’ {aka ‘long int’} to ‘const json5pp::value’ is ambiguous

../../json5pp/json5pp.hpp:179:3: note: candidate: ‘json5pp::value::value(json5pp::value::integer_type)’
  179 |   value(integer_type integer) noexcept : type(TYPE_INTEGER), content(integer) {}
      |   ^~~~~
../../json5pp/json5pp.hpp:173:3: note: candidate: ‘json5pp::value::value(json5pp::value::number_type)’
  173 |   value(number_type number) noexcept : type(TYPE_NUMBER), content(number) {}
      |   ^~~~~
../../json5pp/json5pp.hpp:167:3: note: candidate: ‘json5pp::value::value(json5pp::value::boolean_type)’
  167 |   value(boolean_type boolean) noexcept : type(TYPE_BOOLEAN), content(boolean) {}
      |   ^~~~~

It seems json5pp's integer_type is int. Not sure if there's a way to accommodate every integer types.

For now I need to explicitly specify a larger integer type in the header for 64-bit integers. In my case, changing this:
using integer_type: int;
to
using integer_type: long long;
would enable me to use it with long long type to accommodate 64-bit integers. However, this has a price: all integers must be explicitly cast to long long regardless, and immediate values, like 0, need to be written as 0LL.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.