#ifndef XRPL_JSON_OBJECT_H_INCLUDED #define XRPL_JSON_OBJECT_H_INCLUDED #include #include namespace Json { /** Collection is a base class for Array and Object, classes which provide the facade of JSON collections for the O(1) JSON writer, while still using no heap memory and only a very small amount of stack. From http://json.org, JSON has two types of collection: array, and object. Everything else is a *scalar* - a number, a string, a boolean, the special value null, or a legacy Json::Value. Collections must write JSON "as-it-goes" in order to get the strong performance guarantees. This puts restrictions upon API users: 1. Only one collection can be open for change at any one time. This condition is enforced automatically and a std::logic_error thrown if it is violated. 2. A tag may only be used once in an Object. Some objects have many tags, so this condition might be a little expensive. Enforcement of this condition is turned on in debug builds and a std::logic_error is thrown when the tag is added for a second time. Code samples: Writer writer; // An empty object. { Object::Root (writer); } // Outputs {} // An object with one scalar value. { Object::Root root (writer); write["hello"] = "world"; } // Outputs {"hello":"world"} // Same, using chaining. { Object::Root (writer)["hello"] = "world"; } // Output is the same. // Add several scalars, with chaining. { Object::Root (writer) .set ("hello", "world") .set ("flag", false) .set ("x", 42); } // Outputs {"hello":"world","flag":false,"x":42} // Add an array. { Object::Root root (writer); { auto array = root.setArray ("hands"); array.append ("left"); array.append ("right"); } } // Outputs {"hands":["left", "right"]} // Same, using chaining. { Object::Root (writer) .setArray ("hands") .append ("left") .append ("right"); } // Output is the same. // Add an object. { Object::Root root (writer); { auto object = root.setObject ("hands"); object["left"] = false; object["right"] = true; } } // Outputs {"hands":{"left":false,"right":true}} // Same, using chaining. { Object::Root (writer) .setObject ("hands") .set ("left", false) .set ("right", true); } } // Outputs {"hands":{"left":false,"right":true}} Typical ways to make mistakes and get a std::logic_error: Writer writer; Object::Root root (writer); // Repeat a tag. { root ["hello"] = "world"; root ["hello"] = "there"; // THROWS! in a debug build. } // Open a subcollection, then set something else. { auto object = root.setObject ("foo"); root ["hello"] = "world"; // THROWS! } // Open two subcollections at a time. { auto object = root.setObject ("foo"); auto array = root.setArray ("bar"); // THROWS!! } For more examples, check the unit tests. */ class Collection { public: Collection(Collection&& c) noexcept; Collection& operator=(Collection&& c) noexcept; Collection() = delete; ~Collection(); protected: // A null parent means "no parent at all". // Writers cannot be null. Collection(Collection* parent, Writer*); void checkWritable(std::string const& label); Collection* parent_; Writer* writer_; bool enabled_; }; class Array; //------------------------------------------------------------------------------ /** Represents a JSON object being written to a Writer. */ class Object : protected Collection { public: /** Object::Root is the only Collection that has a public constructor. */ class Root; /** Set a scalar value in the Object for a key. A JSON scalar is a single value - a number, string, boolean, nullptr or a Json::Value. `set()` throws an exception if this object is disabled (which means that one of its children is enabled). In a debug build, `set()` also throws an exception if the key has already been set() before. An operator[] is provided to allow writing `object["key"] = scalar;`. */ template void set(std::string const& key, Scalar const&); void set(std::string const& key, Json::Value const&); // Detail class and method used to implement operator[]. class Proxy; Proxy operator[](std::string const& key); Proxy operator[](Json::StaticString const& key); /** Make a new Object at a key and return it. This Object is disabled until that sub-object is destroyed. Throws an exception if this Object was already disabled. */ Object setObject(std::string const& key); /** Make a new Array at a key and return it. This Object is disabled until that sub-array is destroyed. Throws an exception if this Object was already disabled. */ Array setArray(std::string const& key); protected: friend class Array; Object(Collection* parent, Writer* w) : Collection(parent, w) { } }; class Object::Root : public Object { public: /** Each Object::Root must be constructed with its own unique Writer. */ Root(Writer&); }; //------------------------------------------------------------------------------ /** Represents a JSON array being written to a Writer. */ class Array : private Collection { public: /** Append a scalar to the Arrary. Throws an exception if this array is disabled (which means that one of its sub-collections is enabled). */ template void append(Scalar const&); /** Appends a Json::Value to an array. Throws an exception if this Array was disabled. */ void append(Json::Value const&); /** Append a new Object and return it. This Array is disabled until that sub-object is destroyed. Throws an exception if this Array was disabled. */ Object appendObject(); /** Append a new Array and return it. This Array is disabled until that sub-array is destroyed. Throws an exception if this Array was already disabled. */ Array appendArray(); protected: friend class Object; Array(Collection* parent, Writer* w) : Collection(parent, w) { } }; //------------------------------------------------------------------------------ // Generic accessor functions to allow Json::Value and Collection to // interoperate. /** Add a new subarray at a named key in a Json object. */ Json::Value& setArray(Json::Value&, Json::StaticString const& key); /** Add a new subarray at a named key in a Json object. */ Array setArray(Object&, Json::StaticString const& key); /** Add a new subobject at a named key in a Json object. */ Json::Value& addObject(Json::Value&, Json::StaticString const& key); /** Add a new subobject at a named key in a Json object. */ Object addObject(Object&, Json::StaticString const& key); /** Append a new subarray to a Json array. */ Json::Value& appendArray(Json::Value&); /** Append a new subarray to a Json array. */ Array appendArray(Array&); /** Append a new subobject to a Json object. */ Json::Value& appendObject(Json::Value&); /** Append a new subobject to a Json object. */ Object appendObject(Array&); /** Copy all the keys and values from one object into another. */ void copyFrom(Json::Value& to, Json::Value const& from); /** Copy all the keys and values from one object into another. */ void copyFrom(Object& to, Json::Value const& from); /** An Object that contains its own Writer. */ class WriterObject { public: WriterObject(Output const& output) : writer_(std::make_unique(output)) , object_(std::make_unique(*writer_)) { } WriterObject(WriterObject&& other) = default; Object* operator->() { return object_.get(); } Object& operator*() { return *object_; } private: std::unique_ptr writer_; std::unique_ptr object_; }; WriterObject stringWriterObject(std::string&); //------------------------------------------------------------------------------ // Implementation details. // Detail class for Object::operator[]. class Object::Proxy { private: Object& object_; std::string const key_; public: Proxy(Object& object, std::string const& key); template void operator=(T const& t) { object_.set(key_, t); // Note: This function shouldn't return *this, because it's a trap. // // In Json::Value, foo[jss::key] returns a reference to a // mutable Json::Value contained _inside_ foo. But in the case of // Json::Object, where we write once only, there isn't any such // reference that can be returned. Returning *this would return an // object "a level higher" than in Json::Value, leading to obscure bugs, // particularly in generic code. } }; //------------------------------------------------------------------------------ template void Array::append(Scalar const& value) { checkWritable("append"); if (writer_) writer_->append(value); } template void Object::set(std::string const& key, Scalar const& value) { checkWritable("set"); if (writer_) writer_->set(key, value); } inline Json::Value& setArray(Json::Value& json, Json::StaticString const& key) { return (json[key] = Json::arrayValue); } inline Array setArray(Object& json, Json::StaticString const& key) { return json.setArray(std::string(key)); } inline Json::Value& addObject(Json::Value& json, Json::StaticString const& key) { return (json[key] = Json::objectValue); } inline Object addObject(Object& object, Json::StaticString const& key) { return object.setObject(std::string(key)); } inline Json::Value& appendArray(Json::Value& json) { return json.append(Json::arrayValue); } inline Array appendArray(Array& json) { return json.appendArray(); } inline Json::Value& appendObject(Json::Value& json) { return json.append(Json::objectValue); } inline Object appendObject(Array& json) { return json.appendObject(); } } // namespace Json #endif