From ddf97d6270d0a0da36257bb676d9d05513064de5 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 26 Oct 2021 13:05:59 +0200 Subject: 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 --- source/blender/blenlib/BLI_serialize.hh | 330 +++++++++++++++++++++ source/blender/blenlib/CMakeLists.txt | 4 + source/blender/blenlib/intern/serialize.cc | 217 ++++++++++++++ source/blender/blenlib/tests/BLI_serialize_test.cc | 208 +++++++++++++ 4 files changed, 759 insertions(+) create mode 100644 source/blender/blenlib/BLI_serialize.hh create mode 100644 source/blender/blenlib/intern/serialize.cc create mode 100644 source/blender/blenlib/tests/BLI_serialize_test.cc (limited to 'source/blender/blenlib') diff --git a/source/blender/blenlib/BLI_serialize.hh b/source/blender/blenlib/BLI_serialize.hh new file mode 100644 index 00000000000..0d59f33d552 --- /dev/null +++ b/source/blender/blenlib/BLI_serialize.hh @@ -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 = 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 + +#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 class PrimitiveValue; +using IntValue = PrimitiveValue; +using DoubleValue = PrimitiveValue; +using BooleanValue = PrimitiveValue; + +template 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>, std::shared_ptr, 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>; + +/** + * 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, ObjectElementType, eValueType::Object> { + public: + using LookupValue = std::shared_ptr; + using Lookup = Map; + + /** + * 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 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 deserialize(std::istream &is) override; +}; + +} // namespace blender::io::serialize + diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index c01052f0111..72087a12767 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -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 diff --git a/source/blender/blenlib/intern/serialize.cc b/source/blender/blenlib/intern/serialize.cc new file mode 100644 index 00000000000..1e023a9f782 --- /dev/null +++ b/source/blender/blenlib/intern/serialize.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(this); +} + +const IntValue *Value::as_int_value() const +{ + if (type_ != eValueType::Int) { + return nullptr; + } + return static_cast(this); +} + +const DoubleValue *Value::as_double_value() const +{ + if (type_ != eValueType::Double) { + return nullptr; + } + return static_cast(this); +} + +const BooleanValue *Value::as_boolean_value() const +{ + if (type_ != eValueType::Boolean) { + return nullptr; + } + return static_cast(this); +} + +const ArrayValue *Value::as_array_value() const +{ + if (type_ != eValueType::Array) { + return nullptr; + } + return static_cast(this); +} + +const ObjectValue *Value::as_object_value() const +{ + if (type_ != eValueType::Object) { + return nullptr; + } + return static_cast(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 convert_from_json(const nlohmann::ordered_json &j); +static std::unique_ptr convert_from_json_to_array(const nlohmann::ordered_json &j) +{ + std::unique_ptr array = std::make_unique(); + ArrayValue::Items &elements = array->elements(); + for (auto element : j.items()) { + nlohmann::ordered_json element_json = element.value(); + std::unique_ptr value = convert_from_json(element_json); + elements.append_as(value.release()); + } + return array; +} + +static std::unique_ptr convert_from_json_to_object(const nlohmann::ordered_json &j) +{ + std::unique_ptr object = std::make_unique(); + 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 = convert_from_json(element_json); + elements.append_as(std::pair(key, value.release())); + } + return object; +} + +static std::unique_ptr 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(value); + } + + case nlohmann::json::value_t::null: { + return std::make_unique(); + } + + case nlohmann::json::value_t::boolean: { + return std::make_unique(j); + } + case nlohmann::json::value_t::number_integer: + case nlohmann::json::value_t::number_unsigned: { + return std::make_unique(j); + } + + case nlohmann::json::value_t::number_float: { + return std::make_unique(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(); + } + + BLI_assert_unreachable(); + return std::make_unique(); +} + +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 JsonFormatter::deserialize(std::istream &is) +{ + nlohmann::ordered_json j; + is >> j; + return convert_from_json(j); +} + +} // namespace blender::io::serialize + diff --git a/source/blender/blenlib/tests/BLI_serialize_test.cc b/source/blender/blenlib/tests/BLI_serialize_test.cc new file mode 100644 index 00000000000..8e4ce66fa7c --- /dev/null +++ b/source/blender/blenlib/tests/BLI_serialize_test.cc @@ -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::max(), "2147483647"); + test_int_to_json(std::numeric_limits::min(), "-2147483648"); + test_int_to_json(std::numeric_limits::max(), "9223372036854775807"); + test_int_to_json(std::numeric_limits::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 = 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 + -- cgit v1.2.3