// file : libbuild2/functions-json.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include #include #ifndef BUILD2_BOOTSTRAP # include # include #endif using namespace std; namespace build2 { static size_t array_find_index (const json_value& a, value v) { if (a.type != json_type::array) fail << "expected json array instead of " << to_string (a.type) << " as first argument"; auto b (a.array.begin ()), e (a.array.end ()); auto i (find (b, e, convert (move (v)))); return i != e ? i - b : a.array.size (); }; void json_functions (function_map& m) { function_family f (m, "json"); // $value_type([, ]) // // Return the type of a JSON value: `null`, `boolean`, `number`, `string`, // `array`, or `object`. If the argument is `true`, // then instead of `number` return `signed number`, `unsigned number`, or // `hexadecimal number`. // f["value_type"] += [] (json_value v, optional distinguish_numbers) { bool dn (distinguish_numbers && convert (move (*distinguish_numbers))); return to_string (v.type, dn); }; // $value_size() // // Return the size of a JSON value. // // The size of a `null` value is `0`. The sizes of simple values // (`boolean`, `number`, and `string`) is `1`. The size of `array` and // `object` values is the number of elements and members, respectively. // // Note that the size of a `string` JSON value is not the length of the // string. To get the length call `$string.size()` instead by casting the // JSON value to the `string` value type. // f["value_size"] += [] (json_value v) -> size_t { // Note: should be consistent with value_traits::empty(), // json_subscript(). // switch (v.type) { case json_type::null: return 0; case json_type::boolean: case json_type::signed_number: case json_type::unsigned_number: case json_type::hexadecimal_number: case json_type::string: break; case json_type::array: return v.array.size (); case json_type::object: return v.object.size (); } return 1; }; // $member_name() // // Return the name of a JSON object member. // f["member_name"] += [] (json_value v) { // A member becomes an object with a single member (see json_reverse() // for details). // if (v.type == json_type::object && v.object.size () == 1) return move (v.object.front ().name); fail << "json object member expected instead of " << v.type << endf; }; // $member_value() // // Return the value of a JSON object member. // f["member_value"] += [] (json_value v) { // A member becomes an object with a single member (see json_reverse() // for details). // if (v.type == json_type::object && v.object.size () == 1) { // Reverse simple JSON values to the corresponding fundamental type // values for consistency with subscript/iteration (see // json_subscript_impl() for background). // json_value& jr (v.object.front ().value); switch (jr.type) { #if 0 case json_type::null: return value (names {}); #else case json_type::null: return value (); #endif case json_type::boolean: return value (jr.boolean); case json_type::signed_number: return value (jr.signed_number); case json_type::unsigned_number: case json_type::hexadecimal_number: return value (jr.unsigned_number); case json_type::string: return value (move (jr.string)); case json_type::array: case json_type::object: return value (move (jr)); } } fail << "json object member expected instead of " << v.type << endf; }; // $object_names() // // Return the list of names in the JSON object. If the JSON `null` is // passed instead, assume it is a missing object and return an empty list. // f["object_names"] += [] (json_value o) { names ns; if (o.type == json_type::null) ; else if (o.type == json_type::object) { ns.reserve (o.object.size ()); for (json_member& m: o.object) ns.push_back (name (move (m.name))); } else fail << "expected json object instead of " << to_string (o.type); return ns; }; // $array_size() // // Return the number of elements in the JSON array. If the JSON `null` // value is passed instead, assume it is a missing array and return `0`. // f["array_size"] += [] (json_value a) -> size_t { if (a.type == json_type::null) return 0; if (a.type == json_type::array) return a.array.size (); fail << "expected json array instead of " << to_string (a.type) << endf; }; // $array_find(, ) // // Return true if the JSON array contains the specified JSON value. If the // JSON `null` value is passed instead, assume it is a missing array and // return `false`. // f["array_find"] += [] (json_value a, value v) { if (a.type == json_type::null) return false; size_t i (array_find_index (a, move (v))); return i != a.array.size (); // We now know it's an array. }; // $array_find_index(, ) // // Return the index of the first element in the JSON array that is equal // to the specified JSON value or `$array_size()` if none is // found. If the JSON `null` value is passed instead, assume it is a // missing array and return `0`. // f["array_find_index"] += [](json_value a, value v) -> size_t { if (a.type == json_type::null) return 0; return array_find_index (a, move (v)); }; #ifndef BUILD2_BOOTSTRAP // @@ Flag to support multi-value (returning it as JSON array)? Then // probably also in $serialize(). // // @@ Flag to override duplicates instead of failing? // $json.load() // // Parse the contents of the specified file as JSON input text and return // the result as a value of the `json` type. // // See also `$json.parse()`. // // Note that this function is not pure. // f.insert (".load", false) += [] (names xf) { path f (convert (move (xf))); try { ifdstream is (f); json_parser p (is, f.string ()); return json_value (p); } catch (const invalid_json_input& e) { fail (location (f, e.line, e.column)) << "invalid json input: " << e << info << "byte offset " << e.position << endf; } catch (const io_error& e) { fail << "unable to read from " << f << ": " << e << endf; } }; // $json.parse() // // Parse the specified JSON input text and return the result as a value of // the `json` type. // // See also `$json.load()` and `$json.serialize()`. // f[".parse"] += [] (names text) { string t (convert (move (text))); try { json_parser p (t, nullptr /* name */); return json_value (p); } catch (const invalid_json_input& e) { fail << "invalid json input: " << e << info << "line " << e.line << ", column " << e.column << ", byte offset " << e.position << endf; } }; // $serialize([, ]) // // Serialize the specified JSON value and return the resulting JSON output // text. // // The optional argument specifies the number of indentation // spaces that should be used for pretty-printing. If `0` is passed, then // no pretty-printing is performed. The default is `2` spaces. // // See also `$json.parse()`. // f["serialize"] += [] (json_value v, optional indentation) { uint64_t i (indentation ? convert (*indentation) : 2); try { // For the diagnostics test. // #if 0 if (v.type == json_type::string && v.string == "deadbeef") { v.string[4] = 0xe0; v.string[5] = 0xe0; } #endif string o; json_buffer_serializer s (o, i); v.serialize (s); return o; } catch (const invalid_json_output& e) { diag_record dr; dr << fail << "invalid json value: " << e; if (e.event) dr << info << "while serializing " << to_string (*e.event); if (e.offset != string::npos) dr << info << "offending byte offset " << e.offset; dr << endf; } }; #endif // $size() // $size() // // Return the number of elements in the sequence. // f["size"] += [] (set v) {return v.size ();}; f["size"] += [] (map v) {return v.size ();}; // $keys() // // Return the list of keys in a json map as a json array. // // Note that the result is sorted in ascending order. // f["keys"] += [](map v) { json_value r (json_type::array); r.array.reserve (v.size ()); for (pair& p: v) r.array.push_back (p.first); // @@ PERF: use C++17 map::extract() to steal. return r; }; } }