mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 19:15:54 +00:00
464 lines
12 KiB
C++
464 lines
12 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#ifndef RIPPLE_JSON_OBJECT_H_INCLUDED
|
|
#define RIPPLE_JSON_OBJECT_H_INCLUDED
|
|
|
|
#include <xrpl/json/Writer.h>
|
|
#include <memory>
|
|
|
|
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 <typename Scalar>
|
|
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 <typename Scalar>
|
|
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<Writer>(output))
|
|
, object_(std::make_unique<Object::Root>(*writer_))
|
|
{
|
|
}
|
|
|
|
WriterObject(WriterObject&& other) = default;
|
|
|
|
Object*
|
|
operator->()
|
|
{
|
|
return object_.get();
|
|
}
|
|
|
|
Object&
|
|
operator*()
|
|
{
|
|
return *object_;
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<Writer> writer_;
|
|
std::unique_ptr<Object::Root> 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 <class T>
|
|
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 <typename Scalar>
|
|
void
|
|
Array::append(Scalar const& value)
|
|
{
|
|
checkWritable("append");
|
|
if (writer_)
|
|
writer_->append(value);
|
|
}
|
|
|
|
template <typename Scalar>
|
|
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
|