Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/kimushu/json5pp

JSON (ECMA-404 and JSON5) parser & stringifier for C++11
https://github.com/kimushu/json5pp

cplusplus cplusplus-11 cpp cpp11 ecma404 json json5

Last synced: 5 days ago
JSON representation

JSON (ECMA-404 and JSON5) parser & stringifier for C++11

Awesome Lists containing this project

README

        

# 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)](https://www.json.org/) from `std::istream` or `std::string`.
* Parse [JSON5](https://json5.org/) 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

```cpp
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`)
* object (stored as `std::map`)
* 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](#Examples) for details

## Parse functions

```cpp
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`.

```cpp
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.

```cpp
namespace json5pp {
value parse5(const std::string& str);
}
```
* JSON5 version of `parse(const std::string&)`

```cpp
namespace json5pp {
value parse5(std::istream& istream, bool finish = true);
}
```
* JSON5 version of `parse(std::istream&, bool)`

## Stringify functions

```cpp
namespace json5pp {
class value {
template
std::string stringify(T... manip);
};
}
```
* Stringify value to ECMA-404 standard JSON.
* About `T... manip`, see [iostream overloads and manipulators](#iostream-API-and-manipulators)

```cpp
namespace json5pp {
class value {
template
std::string stringify5(T... manip);
};
}
```
* Stringify value to JSON5.
* About `T... manip`, see [iostream overloads and manipulators](#iostream-API-and-manipulators)

```cpp
namespace json5pp {
template
std::string stringify(const value& v, T... manip);
}
```
* Global method version of `json5pp::value::stringify()`
* Same as `v.stringify(manip...)`

```cpp
namespace json5pp {
template
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>>`
```cpp
std::istream& istream = ...;
json5pp::value v;
istream >> v;
```

### Parse by `operator>>` with manipulators
```cpp
std::istream& istream = ...;
json5pp::value v;
istream >> json5pp::rule::json5() >> v; // Parse as JSON5
```

### Stringify by `operator<<`
```cpp
std::ostream& ostream = ...;
const json5pp::value& v = ...;
ostream << v;
```

### Stringify by `operator<<` with manipulators
```cpp
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:
```json
"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()`
* 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()`
* 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

```cpp
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
```cpp
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&
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&
// 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
```cpp
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&
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&
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::quiet_NaN()); // NaN
json5pp::value falsy5(numeric_limits::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
```cpp
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
```cpp
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