BlenLib: Add JSON Serialization/Deserialization Abstraction Layer.
Adds an abstraction layer to switch between serialization formats. Currently only supports JSON. The abstraction layer supports `String`, `Int`, `Array`, `Null`, `Boolean`, `Float` and `Object`. This feature is only CPP complaint. To write from a stream, the structure can be built by creating a value (any subclass of `blender::io::serialize::Value` can do, and pass it to the `serialize` method of a `blender::io::serialize::Formatter`. The formatter is abstract and there is one implementation for JSON (`JsonFormatter`). To read from a stream use the `deserialize` method of the formatter. {D12693} uses this abstraction layer to read/write asset indexes. Reviewed By: Severin, sybren Maniphest Tasks: T91430 Differential Revision: https://developer.blender.org/D12544
This commit is contained in:
parent
d20fa6c4d4
commit
ddf97d6270
Notes:
blender-bot
2023-02-13 17:41:15 +01:00
Referenced by issue #91430, AssetBrowser: Add JSON/Yaml parsing library
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bli
|
||||
*
|
||||
* An abstraction layer for serialization formats.
|
||||
*
|
||||
* Allowing to read/write data to a serialization format like JSON.
|
||||
*
|
||||
*
|
||||
*
|
||||
* # Supported data types
|
||||
*
|
||||
* The abstraction layer has a limited set of data types it supports.
|
||||
* There are specific classes that builds up the data structure that
|
||||
* can be (de)serialized.
|
||||
*
|
||||
* - StringValue: for strings
|
||||
* - IntValue: for integer values
|
||||
* - DoubleValue: for double precision floating point numbers
|
||||
* - BooleanValue: for boolean values
|
||||
* - ArrayValue: An array of any supported value.
|
||||
* - ObjectValue: A key value pair where keys are std::string.
|
||||
* - NullValue: for null values.
|
||||
*
|
||||
* # Basic usage
|
||||
*
|
||||
* ## Serializing
|
||||
*
|
||||
* - Construct a structure that needs to be serialized using the `*Value` classes.
|
||||
* - Construct the formatter you want to use
|
||||
* - Invoke the formatter.serialize method passing an output stream and the value.
|
||||
*
|
||||
* The next example would format an integer value (42) as JSON the result will
|
||||
* be stored inside `out`.
|
||||
*
|
||||
* ```
|
||||
* JsonFormatter json;
|
||||
* std::stringstream out;
|
||||
* IntValue test_value(42);
|
||||
* json.serialize(out, test_value);
|
||||
* ```
|
||||
*
|
||||
* ## Deserializing
|
||||
*
|
||||
* ```
|
||||
* std::stringstream is("42")
|
||||
* JsonFormatter json;
|
||||
* std::unique_ptr<Value> value = json.deserialize(is);
|
||||
* ```
|
||||
*
|
||||
* # Adding a new formatter
|
||||
*
|
||||
* To add a new formatter a new sub-class of `Formatter` must be created and the
|
||||
* `serialize`/`deserialize` methods should be implemented.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender::io::serialize {
|
||||
|
||||
/**
|
||||
* Enumeration containing all sub-classes of Value. It is used as for type checking.
|
||||
*
|
||||
* \see #Value::type()
|
||||
*/
|
||||
enum class eValueType {
|
||||
String,
|
||||
Int,
|
||||
Array,
|
||||
Null,
|
||||
Boolean,
|
||||
Double,
|
||||
Object,
|
||||
};
|
||||
|
||||
class Value;
|
||||
class StringValue;
|
||||
class ObjectValue;
|
||||
template<typename T, eValueType V> class PrimitiveValue;
|
||||
using IntValue = PrimitiveValue<int64_t, eValueType::Int>;
|
||||
using DoubleValue = PrimitiveValue<double, eValueType::Double>;
|
||||
using BooleanValue = PrimitiveValue<bool, eValueType::Boolean>;
|
||||
|
||||
template<typename Container, typename ContainerItem, eValueType V> class ContainerValue;
|
||||
/* ArrayValue stores its items as shared pointer as it shares data with a lookup table that can
|
||||
* be created by calling `create_lookup`. */
|
||||
using ArrayValue =
|
||||
ContainerValue<Vector<std::shared_ptr<Value>>, std::shared_ptr<Value>, eValueType::Array>;
|
||||
|
||||
/**
|
||||
* Class containing a (de)serializable value.
|
||||
*
|
||||
* To serialize from or to a specific format the Value will be used as an intermediate container
|
||||
* holding the values. Value class is abstract. There are concreate classes to for different data
|
||||
* types.
|
||||
*
|
||||
* - `StringValue`: contains a string.
|
||||
* - `IntValue`: contains an integer.
|
||||
* - `ArrayValue`: contains an array of elements. Elements don't need to be the same type.
|
||||
* - `NullValue`: represents nothing (null pointer or optional).
|
||||
* - `BooleanValue`: contains a boolean (true/false).
|
||||
* - `DoubleValue`: contains a double precision floating point number.
|
||||
* - `ObjectValue`: represents an object (key value pairs where keys are strings and values can be
|
||||
* of different types.
|
||||
*
|
||||
*/
|
||||
class Value {
|
||||
private:
|
||||
eValueType type_;
|
||||
|
||||
protected:
|
||||
Value() = delete;
|
||||
explicit Value(eValueType type) : type_(type)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
virtual ~Value() = default;
|
||||
const eValueType type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts to a StringValue.
|
||||
* Will return nullptr when it is a different type.
|
||||
*/
|
||||
const StringValue *as_string_value() const;
|
||||
|
||||
/**
|
||||
* Casts to an IntValue.
|
||||
* Will return nullptr when it is a different type.
|
||||
*/
|
||||
const IntValue *as_int_value() const;
|
||||
|
||||
/**
|
||||
* Casts to a DoubleValue.
|
||||
* Will return nullptr when it is a different type.
|
||||
*/
|
||||
const DoubleValue *as_double_value() const;
|
||||
|
||||
/**
|
||||
* Casts to a BooleanValue.
|
||||
* Will return nullptr when it is a different type.
|
||||
*/
|
||||
const BooleanValue *as_boolean_value() const;
|
||||
|
||||
/**
|
||||
* Casts to an ArrayValue.
|
||||
* Will return nullptr when it is a different type.
|
||||
*/
|
||||
const ArrayValue *as_array_value() const;
|
||||
|
||||
/**
|
||||
* Casts to an ObjectValue.
|
||||
* Will return nullptr when it is a different type.
|
||||
*/
|
||||
const ObjectValue *as_object_value() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* For generating value types that represent types that are typically known processor data types.
|
||||
*/
|
||||
template<
|
||||
/** Wrapped c/cpp data type that is used to store the value. */
|
||||
typename T,
|
||||
/** Value type of the class. */
|
||||
eValueType V>
|
||||
class PrimitiveValue : public Value {
|
||||
private:
|
||||
T inner_value_{};
|
||||
|
||||
public:
|
||||
explicit PrimitiveValue(const T value) : Value(V), inner_value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
const T value() const
|
||||
{
|
||||
return inner_value_;
|
||||
}
|
||||
};
|
||||
|
||||
class NullValue : public Value {
|
||||
public:
|
||||
NullValue() : Value(eValueType::Null)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class StringValue : public Value {
|
||||
private:
|
||||
std::string string_;
|
||||
|
||||
public:
|
||||
StringValue(const StringRef string) : Value(eValueType::String), string_(string)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &value() const
|
||||
{
|
||||
return string_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Template for arrays and objects.
|
||||
*
|
||||
* Both ArrayValue and ObjectValue store their values in an array.
|
||||
*/
|
||||
template<
|
||||
/** The container type where the elements are stored in. */
|
||||
typename Container,
|
||||
|
||||
/** Type of the data inside the container. */
|
||||
typename ContainerItem,
|
||||
|
||||
/** ValueType representing the value (object/array). */
|
||||
eValueType V>
|
||||
class ContainerValue : public Value {
|
||||
public:
|
||||
using Items = Container;
|
||||
using Item = ContainerItem;
|
||||
|
||||
private:
|
||||
Container inner_value_;
|
||||
|
||||
public:
|
||||
ContainerValue() : Value(V)
|
||||
{
|
||||
}
|
||||
|
||||
const Container &elements() const
|
||||
{
|
||||
return inner_value_;
|
||||
}
|
||||
|
||||
Container &elements()
|
||||
{
|
||||
return inner_value_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal storage type for ObjectValue.
|
||||
*
|
||||
* The elements are stored as an key value pair. The value is a shared pointer so it can be shared
|
||||
* when using `ObjectValue::create_lookup`.
|
||||
*/
|
||||
using ObjectElementType = std::pair<std::string, std::shared_ptr<Value>>;
|
||||
|
||||
/**
|
||||
* Object is a key-value container where the key must be a std::string.
|
||||
* Internally it is stored in a blender::Vector to ensure the order of keys.
|
||||
*/
|
||||
class ObjectValue
|
||||
: public ContainerValue<Vector<ObjectElementType>, ObjectElementType, eValueType::Object> {
|
||||
public:
|
||||
using LookupValue = std::shared_ptr<Value>;
|
||||
using Lookup = Map<std::string, LookupValue>;
|
||||
|
||||
/**
|
||||
* Return a lookup map to quickly lookup by key.
|
||||
*
|
||||
* The lookup is owned by the caller.
|
||||
*/
|
||||
const Lookup create_lookup() const
|
||||
{
|
||||
Lookup result;
|
||||
for (const Item &item : elements()) {
|
||||
result.add_as(item.first, item.second);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for any provided Formatter.
|
||||
*/
|
||||
class Formatter {
|
||||
public:
|
||||
virtual ~Formatter() = default;
|
||||
|
||||
/** Serialize the value to the given stream. */
|
||||
virtual void serialize(std::ostream &os, const Value &value) = 0;
|
||||
|
||||
/** Deserialize the stream. */
|
||||
virtual std::unique_ptr<Value> deserialize(std::istream &is) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatter to (de)serialize a json formatted stream.
|
||||
*/
|
||||
class JsonFormatter : public Formatter {
|
||||
public:
|
||||
/**
|
||||
* The identation level to use.
|
||||
* Typically number of chars. Set to 0 to not use identation.
|
||||
*/
|
||||
int8_t indentation_len = 0;
|
||||
|
||||
public:
|
||||
void serialize(std::ostream &os, const Value &value) override;
|
||||
std::unique_ptr<Value> deserialize(std::istream &is) override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::serialize
|
||||
|
|
@ -27,6 +27,7 @@ set(INC
|
|||
../../../intern/guardedalloc
|
||||
../../../intern/numaapi/include
|
||||
../../../extern/wcwidth
|
||||
../../../extern/json/include
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
|
@ -126,6 +127,7 @@ set(SRC
|
|||
intern/scanfill.c
|
||||
intern/scanfill_utils.c
|
||||
intern/session_uuid.c
|
||||
intern/serialize.cc
|
||||
intern/smallhash.c
|
||||
intern/sort.c
|
||||
intern/sort_utils.c
|
||||
|
@ -282,6 +284,7 @@ set(SRC
|
|||
BLI_session_uuid.h
|
||||
BLI_set.hh
|
||||
BLI_set_slots.hh
|
||||
BLI_serialize.hh
|
||||
BLI_simd.h
|
||||
BLI_smallhash.h
|
||||
BLI_sort.h
|
||||
|
@ -448,6 +451,7 @@ if(WITH_GTESTS)
|
|||
tests/BLI_span_test.cc
|
||||
tests/BLI_stack_cxx_test.cc
|
||||
tests/BLI_stack_test.cc
|
||||
tests/BLI_serialize_test.cc
|
||||
tests/BLI_string_ref_test.cc
|
||||
tests/BLI_string_search_test.cc
|
||||
tests/BLI_string_test.cc
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
#include "BLI_serialize.hh"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace blender::io::serialize {
|
||||
|
||||
const StringValue *Value::as_string_value() const
|
||||
{
|
||||
if (type_ != eValueType::String) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const StringValue *>(this);
|
||||
}
|
||||
|
||||
const IntValue *Value::as_int_value() const
|
||||
{
|
||||
if (type_ != eValueType::Int) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const IntValue *>(this);
|
||||
}
|
||||
|
||||
const DoubleValue *Value::as_double_value() const
|
||||
{
|
||||
if (type_ != eValueType::Double) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const DoubleValue *>(this);
|
||||
}
|
||||
|
||||
const BooleanValue *Value::as_boolean_value() const
|
||||
{
|
||||
if (type_ != eValueType::Boolean) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const BooleanValue *>(this);
|
||||
}
|
||||
|
||||
const ArrayValue *Value::as_array_value() const
|
||||
{
|
||||
if (type_ != eValueType::Array) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const ArrayValue *>(this);
|
||||
}
|
||||
|
||||
const ObjectValue *Value::as_object_value() const
|
||||
{
|
||||
if (type_ != eValueType::Object) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const ObjectValue *>(this);
|
||||
}
|
||||
|
||||
static void convert_to_json(nlohmann::ordered_json &j, const Value &value);
|
||||
static void convert_to_json(nlohmann::ordered_json &j, const ArrayValue &value)
|
||||
{
|
||||
const ArrayValue::Items &items = value.elements();
|
||||
/* Create a json array to store the elements. If this isn't done and items is empty it would
|
||||
* return use a null value, in stead of an empty array. */
|
||||
j = "[]"_json;
|
||||
for (const ArrayValue::Item &item_value : items) {
|
||||
nlohmann::ordered_json json_item;
|
||||
convert_to_json(json_item, *item_value);
|
||||
j.push_back(json_item);
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_to_json(nlohmann::ordered_json &j, const ObjectValue &value)
|
||||
{
|
||||
const ObjectValue::Items &attributes = value.elements();
|
||||
/* Create a json object to store the attributes. If this isn't done and attributes is empty it
|
||||
* would return use a null value, in stead of an empty object. */
|
||||
j = "{}"_json;
|
||||
for (const ObjectValue::Item &attribute : attributes) {
|
||||
nlohmann::ordered_json json_item;
|
||||
convert_to_json(json_item, *attribute.second);
|
||||
j[attribute.first] = json_item;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_to_json(nlohmann::ordered_json &j, const Value &value)
|
||||
{
|
||||
switch (value.type()) {
|
||||
case eValueType::String: {
|
||||
j = value.as_string_value()->value();
|
||||
break;
|
||||
}
|
||||
|
||||
case eValueType::Int: {
|
||||
j = value.as_int_value()->value();
|
||||
break;
|
||||
}
|
||||
|
||||
case eValueType::Array: {
|
||||
const ArrayValue &array = *value.as_array_value();
|
||||
convert_to_json(j, array);
|
||||
break;
|
||||
}
|
||||
|
||||
case eValueType::Object: {
|
||||
const ObjectValue &object = *value.as_object_value();
|
||||
convert_to_json(j, object);
|
||||
break;
|
||||
}
|
||||
|
||||
case eValueType::Null: {
|
||||
j = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
case eValueType::Boolean: {
|
||||
j = value.as_boolean_value()->value();
|
||||
break;
|
||||
}
|
||||
|
||||
case eValueType::Double: {
|
||||
j = value.as_double_value()->value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<Value> convert_from_json(const nlohmann::ordered_json &j);
|
||||
static std::unique_ptr<ArrayValue> convert_from_json_to_array(const nlohmann::ordered_json &j)
|
||||
{
|
||||
std::unique_ptr<ArrayValue> array = std::make_unique<ArrayValue>();
|
||||
ArrayValue::Items &elements = array->elements();
|
||||
for (auto element : j.items()) {
|
||||
nlohmann::ordered_json element_json = element.value();
|
||||
std::unique_ptr<Value> value = convert_from_json(element_json);
|
||||
elements.append_as(value.release());
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static std::unique_ptr<ObjectValue> convert_from_json_to_object(const nlohmann::ordered_json &j)
|
||||
{
|
||||
std::unique_ptr<ObjectValue> object = std::make_unique<ObjectValue>();
|
||||
ObjectValue::Items &elements = object->elements();
|
||||
for (auto element : j.items()) {
|
||||
std::string key = element.key();
|
||||
nlohmann::ordered_json element_json = element.value();
|
||||
std::unique_ptr<Value> value = convert_from_json(element_json);
|
||||
elements.append_as(std::pair(key, value.release()));
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
static std::unique_ptr<Value> convert_from_json(const nlohmann::ordered_json &j)
|
||||
{
|
||||
switch (j.type()) {
|
||||
case nlohmann::json::value_t::array: {
|
||||
return convert_from_json_to_array(j);
|
||||
}
|
||||
|
||||
case nlohmann::json::value_t::object: {
|
||||
return convert_from_json_to_object(j);
|
||||
}
|
||||
|
||||
case nlohmann::json::value_t::string: {
|
||||
std::string value = j;
|
||||
return std::make_unique<StringValue>(value);
|
||||
}
|
||||
|
||||
case nlohmann::json::value_t::null: {
|
||||
return std::make_unique<NullValue>();
|
||||
}
|
||||
|
||||
case nlohmann::json::value_t::boolean: {
|
||||
return std::make_unique<BooleanValue>(j);
|
||||
}
|
||||
case nlohmann::json::value_t::number_integer:
|
||||
case nlohmann::json::value_t::number_unsigned: {
|
||||
return std::make_unique<IntValue>(j);
|
||||
}
|
||||
|
||||
case nlohmann::json::value_t::number_float: {
|
||||
return std::make_unique<DoubleValue>(j);
|
||||
}
|
||||
|
||||
case nlohmann::json::value_t::binary:
|
||||
case nlohmann::json::value_t::discarded:
|
||||
/*
|
||||
* Binary data isn't supported.
|
||||
* Discarded is an internal type of nlohmann.
|
||||
*
|
||||
* Assert in case we need to parse them.
|
||||
*/
|
||||
BLI_assert_unreachable();
|
||||
return std::make_unique<NullValue>();
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return std::make_unique<NullValue>();
|
||||
}
|
||||
|
||||
void JsonFormatter::serialize(std::ostream &os, const Value &value)
|
||||
{
|
||||
nlohmann::ordered_json j;
|
||||
convert_to_json(j, value);
|
||||
if (indentation_len) {
|
||||
os << j.dump(indentation_len);
|
||||
}
|
||||
else {
|
||||
os << j.dump();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Value> JsonFormatter::deserialize(std::istream &is)
|
||||
{
|
||||
nlohmann::ordered_json j;
|
||||
is >> j;
|
||||
return convert_from_json(j);
|
||||
}
|
||||
|
||||
} // namespace blender::io::serialize
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/* Apache License, Version 2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "BLI_serialize.hh"
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* tests */
|
||||
|
||||
namespace blender::io::serialize::json::testing {
|
||||
|
||||
TEST(serialize, string_to_json)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
StringValue test_value("Hello JSON");
|
||||
json.serialize(out, test_value);
|
||||
EXPECT_EQ(out.str(), "\"Hello JSON\"");
|
||||
}
|
||||
|
||||
static void test_int_to_json(int64_t value, StringRef expected)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
IntValue test_value(value);
|
||||
json.serialize(out, test_value);
|
||||
EXPECT_EQ(out.str(), expected);
|
||||
}
|
||||
|
||||
TEST(serialize, int_to_json)
|
||||
{
|
||||
test_int_to_json(42, "42");
|
||||
test_int_to_json(-42, "-42");
|
||||
test_int_to_json(std::numeric_limits<int32_t>::max(), "2147483647");
|
||||
test_int_to_json(std::numeric_limits<int32_t>::min(), "-2147483648");
|
||||
test_int_to_json(std::numeric_limits<int64_t>::max(), "9223372036854775807");
|
||||
test_int_to_json(std::numeric_limits<int64_t>::min(), "-9223372036854775808");
|
||||
}
|
||||
|
||||
TEST(serialize, double_to_json)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
DoubleValue test_value(42.31);
|
||||
json.serialize(out, test_value);
|
||||
EXPECT_EQ(out.str(), "42.31");
|
||||
}
|
||||
|
||||
TEST(serialize, null_to_json)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
NullValue test_value;
|
||||
json.serialize(out, test_value);
|
||||
EXPECT_EQ(out.str(), "null");
|
||||
}
|
||||
|
||||
TEST(serialize, false_to_json)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
BooleanValue value(false);
|
||||
json.serialize(out, value);
|
||||
EXPECT_EQ(out.str(), "false");
|
||||
}
|
||||
|
||||
TEST(serialize, true_to_json)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
BooleanValue value(true);
|
||||
json.serialize(out, value);
|
||||
EXPECT_EQ(out.str(), "true");
|
||||
}
|
||||
|
||||
TEST(serialize, array_to_json)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
ArrayValue value_array;
|
||||
ArrayValue::Items &array = value_array.elements();
|
||||
array.append_as(new IntValue(42));
|
||||
array.append_as(new StringValue("Hello JSON"));
|
||||
array.append_as(new NullValue);
|
||||
array.append_as(new BooleanValue(false));
|
||||
array.append_as(new BooleanValue(true));
|
||||
|
||||
json.serialize(out, value_array);
|
||||
EXPECT_EQ(out.str(), "[42,\"Hello JSON\",null,false,true]");
|
||||
}
|
||||
|
||||
TEST(serialize, object_to_json)
|
||||
{
|
||||
JsonFormatter json;
|
||||
std::stringstream out;
|
||||
ObjectValue value_object;
|
||||
ObjectValue::Items &attributes = value_object.elements();
|
||||
attributes.append_as(std::pair(std::string("best_number"), new IntValue(42)));
|
||||
|
||||
json.serialize(out, value_object);
|
||||
EXPECT_EQ(out.str(), "{\"best_number\":42}");
|
||||
}
|
||||
|
||||
TEST(serialize, json_roundtrip_ordering)
|
||||
{
|
||||
const std::string input =
|
||||
"[{\"_id\":\"614ada7c476c472ecbd0ecbb\",\"index\":0,\"guid\":\"d5b81381-cef8-4327-923d-"
|
||||
"41e57ff79326\",\"isActive\":false,\"balance\":\"$2,062.25\",\"picture\":\"http://"
|
||||
"placehold.it/32x32\",\"age\":26,\"eyeColor\":\"brown\",\"name\":\"Geneva "
|
||||
"Vega\",\"gender\":\"female\",\"company\":\"SLOGANAUT\",\"email\":\"genevavega@sloganaut."
|
||||
"com\",\"phone\":\"+1 (993) 432-2805\",\"address\":\"943 Christopher Avenue, Northchase, "
|
||||
"Alabama, 5769\",\"about\":\"Eu cillum qui eu fugiat sit nulla eu duis. Aliqua nulla aliqua "
|
||||
"ea tempor dolor fugiat sint consectetur exercitation ipsum magna ex. Aute laborum esse "
|
||||
"magna nostrud in cillum et mollit proident. Deserunt ex minim adipisicing incididunt "
|
||||
"incididunt dolore velit aliqua.\\r\\n\",\"registered\":\"2014-06-02T06:29:33 "
|
||||
"-02:00\",\"latitude\":-66.003108,\"longitude\":44.038986,\"tags\":[\"exercitation\","
|
||||
"\"laborum\",\"velit\",\"magna\",\"officia\",\"aliqua\",\"laboris\"],\"friends\":[{\"id\":0,"
|
||||
"\"name\":\"Daniel Stuart\"},{\"id\":1,\"name\":\"Jackson "
|
||||
"Velez\"},{\"id\":2,\"name\":\"Browning Boyd\"}],\"greeting\":\"Hello, Geneva Vega! You "
|
||||
"have 8 unread "
|
||||
"messages.\",\"favoriteFruit\":\"strawberry\"},{\"_id\":\"614ada7cf28685063c6722af\","
|
||||
"\"index\":1,\"guid\":\"e157edf3-a86d-4984-b18d-e2fe568a9915\",\"isActive\":false,"
|
||||
"\"balance\":\"$3,550.44\",\"picture\":\"http://placehold.it/"
|
||||
"32x32\",\"age\":40,\"eyeColor\":\"blue\",\"name\":\"Lamb "
|
||||
"Lowe\",\"gender\":\"male\",\"company\":\"PROXSOFT\",\"email\":\"lamblowe@proxsoft.com\","
|
||||
"\"phone\":\"+1 (999) 573-2855\",\"address\":\"632 Rockwell Place, Diaperville, "
|
||||
"Pennsylvania, 5050\",\"about\":\"Anim dolor deserunt esse quis velit adipisicing aute "
|
||||
"nostrud velit minim culpa aute et tempor. Dolor aliqua reprehenderit anim voluptate. "
|
||||
"Consequat proident ut culpa reprehenderit qui. Nisi proident velit cillum voluptate. "
|
||||
"Ullamco id sunt quis aute adipisicing cupidatat consequat "
|
||||
"aliquip.\\r\\n\",\"registered\":\"2014-09-06T06:13:36 "
|
||||
"-02:00\",\"latitude\":-44.550228,\"longitude\":-80.893356,\"tags\":[\"anim\",\"id\","
|
||||
"\"irure\",\"do\",\"officia\",\"irure\",\"Lorem\"],\"friends\":[{\"id\":0,\"name\":"
|
||||
"\"Faulkner Watkins\"},{\"id\":1,\"name\":\"Cecile Schneider\"},{\"id\":2,\"name\":\"Burt "
|
||||
"Lester\"}],\"greeting\":\"Hello, Lamb Lowe! You have 1 unread "
|
||||
"messages.\",\"favoriteFruit\":\"strawberry\"},{\"_id\":\"614ada7c235335fc56bc2f78\","
|
||||
"\"index\":2,\"guid\":\"8206bad1-8274-49fd-9223-d727589f22ca\",\"isActive\":false,"
|
||||
"\"balance\":\"$2,548.34\",\"picture\":\"http://placehold.it/"
|
||||
"32x32\",\"age\":37,\"eyeColor\":\"blue\",\"name\":\"Sallie "
|
||||
"Chase\",\"gender\":\"female\",\"company\":\"FLEETMIX\",\"email\":\"salliechase@fleetmix."
|
||||
"com\",\"phone\":\"+1 (953) 453-3388\",\"address\":\"865 Irving Place, Chelsea, Utah, "
|
||||
"9777\",\"about\":\"In magna exercitation incididunt exercitation dolor anim. Consectetur "
|
||||
"dolore commodo elit cillum dolor reprehenderit magna minim et ex labore pariatur. Nulla "
|
||||
"ullamco officia velit in aute proident nostrud. Duis deserunt et labore Lorem aliqua "
|
||||
"eiusmod commodo sunt.\\r\\n\",\"registered\":\"2017-03-16T08:54:53 "
|
||||
"-01:00\",\"latitude\":-78.481939,\"longitude\":-149.820215,\"tags\":[\"Lorem\",\"ipsum\","
|
||||
"\"in\",\"tempor\",\"consectetur\",\"voluptate\",\"elit\"],\"friends\":[{\"id\":0,\"name\":"
|
||||
"\"Gibson Garner\"},{\"id\":1,\"name\":\"Anna Frank\"},{\"id\":2,\"name\":\"Roberson "
|
||||
"Daugherty\"}],\"greeting\":\"Hello, Sallie Chase! You have 7 unread "
|
||||
"messages.\",\"favoriteFruit\":\"apple\"},{\"_id\":\"614ada7c93b63ecad5f9ba5e\",\"index\":3,"
|
||||
"\"guid\":\"924b02fc-7c27-481a-9941-db3b9403dfe1\",\"isActive\":true,\"balance\":\"$1,633."
|
||||
"60\",\"picture\":\"http://placehold.it/"
|
||||
"32x32\",\"age\":29,\"eyeColor\":\"brown\",\"name\":\"Grace "
|
||||
"Mccall\",\"gender\":\"female\",\"company\":\"PIVITOL\",\"email\":\"gracemccall@pivitol."
|
||||
"com\",\"phone\":\"+1 (964) 541-2514\",\"address\":\"734 Schaefer Street, Topaz, Virginia, "
|
||||
"9137\",\"about\":\"Amet officia magna fugiat ut pariatur fugiat elit culpa voluptate elit "
|
||||
"do proident culpa minim. Commodo do minim reprehenderit ut voluptate ut velit id esse "
|
||||
"consequat. Labore ullamco deserunt irure eiusmod cillum tempor incididunt qui adipisicing "
|
||||
"nostrud pariatur enim aliquip. Excepteur nostrud commodo consectetur esse duis irure "
|
||||
"qui.\\r\\n\",\"registered\":\"2015-04-24T03:55:17 "
|
||||
"-02:00\",\"latitude\":58.801446,\"longitude\":-157.413865,\"tags\":[\"do\",\"ea\",\"eu\","
|
||||
"\"eu\",\"qui\",\"duis\",\"sint\"],\"friends\":[{\"id\":0,\"name\":\"Carrie "
|
||||
"Short\"},{\"id\":1,\"name\":\"Dickerson Barnes\"},{\"id\":2,\"name\":\"Rae "
|
||||
"Rios\"}],\"greeting\":\"Hello, Grace Mccall! You have 5 unread "
|
||||
"messages.\",\"favoriteFruit\":\"apple\"},{\"_id\":\"614ada7c9caf1353b0e22bbf\",\"index\":4,"
|
||||
"\"guid\":\"e5981ae1-90e4-41c4-9905-161522db700b\",\"isActive\":false,\"balance\":\"$3,660."
|
||||
"34\",\"picture\":\"http://placehold.it/"
|
||||
"32x32\",\"age\":31,\"eyeColor\":\"blue\",\"name\":\"Herring "
|
||||
"Powers\",\"gender\":\"male\",\"company\":\"PYRAMIA\",\"email\":\"herringpowers@pyramia."
|
||||
"com\",\"phone\":\"+1 (981) 541-2829\",\"address\":\"409 Furman Avenue, Waterloo, South "
|
||||
"Carolina, 380\",\"about\":\"In officia culpa aliqua culpa pariatur aliqua mollit ex. Velit "
|
||||
"est Lorem enim magna cillum sunt elit consectetur deserunt ea est consectetur fugiat "
|
||||
"mollit. Aute Lorem excepteur minim esse qui. Id Lorem in tempor et. Nisi aliquip laborum "
|
||||
"magna eu aute.\\r\\n\",\"registered\":\"2018-07-05T07:28:54 "
|
||||
"-02:00\",\"latitude\":51.497405,\"longitude\":-129.422711,\"tags\":[\"eiusmod\",\"et\","
|
||||
"\"nostrud\",\"reprehenderit\",\"Lorem\",\"cillum\",\"nulla\"],\"friends\":[{\"id\":0,"
|
||||
"\"name\":\"Tonia Keith\"},{\"id\":1,\"name\":\"Leanne Rice\"},{\"id\":2,\"name\":\"Craig "
|
||||
"Gregory\"}],\"greeting\":\"Hello, Herring Powers! You have 6 unread "
|
||||
"messages.\",\"favoriteFruit\":\"strawberry\"},{\"_id\":\"614ada7c53a3d6da77468f25\","
|
||||
"\"index\":5,\"guid\":\"abb2eec9-c4f0-4a0d-b20a-5c8e50fe88a1\",\"isActive\":true,"
|
||||
"\"balance\":\"$1,481.08\",\"picture\":\"http://placehold.it/"
|
||||
"32x32\",\"age\":31,\"eyeColor\":\"green\",\"name\":\"Lela "
|
||||
"Dillard\",\"gender\":\"female\",\"company\":\"CEMENTION\",\"email\":\"leladillard@"
|
||||
"cemention.com\",\"phone\":\"+1 (856) 456-3657\",\"address\":\"391 Diamond Street, Madaket, "
|
||||
"Ohio, 9337\",\"about\":\"Tempor dolor ullamco esse cillum excepteur. Excepteur aliqua non "
|
||||
"enim anim esse amet cupidatat non. Cillum excepteur occaecat cupidatat elit labore. "
|
||||
"Pariatur ut esse sint elit. Velit sint magna et commodo sit velit labore consectetur irure "
|
||||
"officia proident aliquip. Aliqua dolore ipsum voluptate veniam deserunt amet irure. Cillum "
|
||||
"consequat veniam proident Lorem in anim enim veniam ea "
|
||||
"nulla.\\r\\n\",\"registered\":\"2017-01-11T11:07:22 "
|
||||
"-01:00\",\"latitude\":86.349081,\"longitude\":-179.983754,\"tags\":[\"consequat\","
|
||||
"\"labore\",\"consectetur\",\"dolor\",\"laborum\",\"eiusmod\",\"in\"],\"friends\":[{\"id\":"
|
||||
"0,\"name\":\"Hancock Rivera\"},{\"id\":1,\"name\":\"Chasity "
|
||||
"Oneil\"},{\"id\":2,\"name\":\"Whitaker Barr\"}],\"greeting\":\"Hello, Lela Dillard! You "
|
||||
"have 3 unread messages.\",\"favoriteFruit\":\"strawberry\"}]";
|
||||
std::stringstream is(input);
|
||||
|
||||
JsonFormatter json;
|
||||
std::unique_ptr<Value> value = json.deserialize(is);
|
||||
EXPECT_EQ(value->type(), eValueType::Array);
|
||||
|
||||
std::stringstream out;
|
||||
json.serialize(out, *value);
|
||||
EXPECT_EQ(out.str(), input);
|
||||
}
|
||||
|
||||
} // namespace blender::io::serialize::json::testing
|
||||
|
Loading…
Reference in New Issue