diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2024-02-06 05:22:12 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2024-02-07 15:02:38 +0200 |
commit | 36d6b4e5549dc45baf890105de5ef487211f0144 (patch) | |
tree | 762f9eba621026e9bb7d8fd69107a4447783a45a /libbuild2/json.hxx | |
parent | a5acaba537dab8e06be1197916acff86699aa5a3 (diff) |
Add experimental support for JSON value types
New types:
json
json_array
json_object
New functions:
$json.value_type(<json>)
$json.value_size(<json>)
$json.member_{name,value}(<json-member>)
$json.object_names(<json-object>)
$json.array_size(<json-array>)
$json.array_find(<json-array>, <json>)
$json.array_find_index(<json-array>, <json>)
$json.load(<path>)
$json.parse(<text>)
$json.serialize(<json>[, <indentation>])
For example, to load a JSON value from a file:
j = $json.load($src_base/board.json)
Or to construct it in a buildfile:
j = [json] one@1 two@([json] 2 3 4) three@([json] x@1 y@-1)
This can also be done incrementally with append/prepend:
j = [json_object]
j += one@1
j += two@([json] 2 3 4)
j += three@([json] x@1 y@-1)
Instead of using this JSON-like syntax, one can also specify valid JSON
input text:
j = [json] '{"one":1, "two":[2, 3, 4], "three":{"x":1, "y":-1}'
Besides the above set of functions, other handy ways to access components
in a JSON value are iteration and subscript. For example:
for m: $j
print $member_name($m) $member_value($m)
print ($j[three])
A subscript can be nested:
print ($j[two][1])
print ($j[three][x])
While a JSON value can be printed directly like any other value, the
representation will not be pretty-printed. As a result, for complex
JSON values, printing a serialized representation might be a more
readable option:
info $serialize($j)
Diffstat (limited to 'libbuild2/json.hxx')
-rw-r--r-- | libbuild2/json.hxx | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/libbuild2/json.hxx b/libbuild2/json.hxx new file mode 100644 index 0000000..1f2694d --- /dev/null +++ b/libbuild2/json.hxx @@ -0,0 +1,316 @@ +// file : libbuild2/json.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_JSON_HXX +#define LIBBUILD2_JSON_HXX + +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/export.hxx> + +namespace butl +{ + namespace json + { + enum class event: uint8_t; + class parser; + class buffer_serializer; + class stream_serializer; + class invalid_json_input; + class invalid_json_output; + } +} + +namespace build2 +{ + using json_event = butl::json::event; + using json_parser = butl::json::parser; + using json_buffer_serializer = butl::json::buffer_serializer; + using json_stream_serializer = butl::json::stream_serializer; + using butl::json::invalid_json_input; + using butl::json::invalid_json_output; + +#ifndef BUILD2_BOOTSTRAP + LIBBUILD2_SYMEXPORT const char* + to_string (json_event); +#endif + + // @@ TODO: + // + // - provide swap(). + // - provide operator=(uint64_t), etc. + // - provide std::hash specialization + // - tighted at()/[] interface in json_array and json_object + // - tighten noexcep where possible + // - operator bool() - in a sense null is like nullopt. + // + + // This JSON representation has one extensions compared to the standard JSON + // model: it distinguishes between signed, unsigned, and hexadecimal + // numbers. + // + // Note also that we don't assume that object members are in a sorted order + // (but do assume there are no duplicates). However, we could add an + // argument to signal that this is the case to speed up some functions, for + // example, compare(). + // + enum class json_type: uint8_t + { + null, // Note: keep first for comparison. + boolean, + signed_number, + unsigned_number, + hexadecimal_number, + string, + array, + object, + }; + + // Return the JSON type as string. If distinguish_numbers is true, then + // distinguish between the singned, unsigned, and hexadecimal types. + // + LIBBUILD2_SYMEXPORT const char* + to_string (json_type, bool distinguish_numbers = false) noexcept; + + inline ostream& + operator<< (ostream& os, json_type t) {return os << to_string (t);} + + struct json_member; + + class LIBBUILD2_SYMEXPORT json_value + { + public: + using string_type = build2::string; + using array_type = vector<json_value>; + using object_type = vector<json_member>; + + json_type type; + + union + { + bool boolean; + int64_t signed_number; + uint64_t unsigned_number; // Also used for hexadecimal_number. + string_type string; + array_type array; + object_type object; + }; + + explicit + json_value (json_type = json_type::null) noexcept; + + explicit + json_value (std::nullptr_t) noexcept; + + explicit + json_value (bool) noexcept; + + explicit + json_value (int64_t) noexcept; + + explicit + json_value (uint64_t, bool hexadecimal = false) noexcept; + + explicit + json_value (string_type); + + // If the expected type is specfied, then fail if it does not match + // parsed. Throws invalid_json_input. + // + explicit + json_value (json_parser&, optional<json_type> expected = {}); + + // If the expected type is specfied, then fail if it does not match the + // value's. Throws invalid_json_output. + // + void + serialize (json_buffer_serializer&, + optional<json_type> expected = {}) const; + + // Note that values of different types are never equal, except for + // signed/unsigned/hexadecimal numbers. Null is equal to null and is less + // than any other value. Arrays are compared lexicographically. Object + // members are considered in the lexicographically-compared name-ascending + // order (see RFC8785). An absent member is less than a present member + // (even if it's null). + // + int + compare (const json_value&) const noexcept; + + // Append/prepend one JSON value to another. Throw invalid_argument if the + // values are incompatible. Note that for numbers this can also lead to + // the change of the value type. + // + // Append/prepend to an object overrides existing members. Append/prepend + // an array to an array splices in the array elements rather than adding + // an element of the array type. + // + void + append (json_value&&); + + void + prepend (json_value&&); + + + // Array element access. + // + // If the index is out of array bounds, the at() functions throw + // std::out_of_range, the const operator[] returns null_json_value, and + // the non-const operator[] inserts a new null value at the specified + // position (filling any missing elements in between with nulls) and + // returns that. All three functions throw std::invalid_argument if the + // value is not an array or null with null treated as (missing) array + // rather than wrong value type (and with at() functons throwing + // out_of_range in this case). + // + // Note that non-const operator[] will not only insert a new element but + // will also turn the value it is called upon into array if it is null. + // This semantics allows you to string several subscripts to build up a + // chain of values. + // + // Note also that while the operator[] interface is convenient for + // accessing and modifying (or building up) values deep in the tree, it + // can lead to inefficiencies or even undesirable semantics during + // otherwise read-only access of a non-const object due to the potential + // insertion of null values for missing array elements. As a result, it's + // recommended to alwas use a const reference for read-only access (or use + // the at() interface if this is deemed too easy to forget). + // + const json_value& + at (size_t) const; + + json_value& + at (size_t); + + const json_value& + operator[] (size_t) const; + + json_value& + operator[] (size_t); + + + // Object member access. + // + // If a member with the specified name is not found in the object, the + // at() functions throw std::out_of_range, the const operator[] returns + // null_json_value, and the non-const operator[] adds a new member with + // the specified name and null value and returns that value. All three + // functions throw std::invalid_argument if the value is not an array or + // null with null treated as (missing) object rather than wrong value type + // (and with at() functons throwing out_of_range in this case). + // + // Note that non-const operator[] will not only insert a new member but + // will also turn the value it is called upon into object if it is null. + // This semantics allows you to string several subscripts to build up a + // chain of values. + // + // Note also that while the operator[] interface is convenient for + // accessing and modifying (or building up) values deep in the tree, it + // can lead to inefficiencies or even undesirable semantics during + // otherwise read-only access of a non-const object due to the potential + // insertion of null values for missing object members. As a result, it's + // recommended to alwas use a const reference for read-only access (or use + // the at() interface if this is deemed too easy to forget). + // + const json_value& + at (const char*) const; + + json_value& + at (const char*); + + const json_value& + operator[] (const char*) const; + + json_value& + operator[] (const char*); + + const json_value& + at (const string_type&) const; + + json_value& + at (const string_type&); + + const json_value& + operator[] (const string_type&) const; + + json_value& + operator[] (const string_type&); + + // Note that the moved-from value becomes JSON null value. + // + json_value (json_value&&) noexcept; + json_value (const json_value&); + + json_value& operator= (json_value&&) noexcept; + json_value& operator= (const json_value&); + + ~json_value () noexcept; + }; + + LIBBUILD2_SYMEXPORT extern const json_value null_json_value; + + inline bool + operator== (const json_value& x, const json_value& y) {return x.compare (y) == 0;} + + inline bool + operator!= (const json_value& x, const json_value& y) {return !(x == y);} + + inline bool + operator< (const json_value& x, const json_value& y) {return x.compare (y) < 0;} + + inline bool + operator<= (const json_value& x, const json_value& y) {return x.compare (y) <= 0;} + + inline bool + operator> (const json_value& x, const json_value& y) {return !(x <= y);} + + inline bool + operator>= (const json_value& x, const json_value& y) {return !(x < y);} + + // A JSON object member. + // + struct json_member + { + // @@ TODO: add some convenience constructors? + + string name; + json_value value; + }; + + // A JSON value that can only be an array. + // + class /*LIBBUILD2_SYMEXPORT*/ json_array: public json_value + { + public: + // Create empty array. + // + json_array () noexcept; + + explicit + json_array (json_parser&); + + void + serialize (json_buffer_serializer& s) const; + }; + + // A JSON value that can only be an object. + // + class /*LIBBUILD2_SYMEXPORT*/ json_object: public json_value + { + public: + // Create empty object. + // + json_object () noexcept; + + explicit + json_object (json_parser&); + + void + serialize (json_buffer_serializer& s) const; + }; +} + +#include <libbuild2/json.ixx> + +#endif // LIBBUILD2_JSON_HXX |