[Bf-blender-cvs] [ddf97d6270d] master: BlenLib: Add JSON Serialization/Deserialization Abstraction Layer.

Jeroen Bakker noreply at git.blender.org
Tue Oct 26 13:09:31 CEST 2021


Commit: ddf97d6270d0a0da36257bb676d9d05513064de5
Author: Jeroen Bakker
Date:   Tue Oct 26 13:05:59 2021 +0200
Branches: master
https://developer.blender.org/rBddf97d6270d0a0da36257bb676d9d05513064de5

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

===================================================================

A	source/blender/blenlib/BLI_serialize.hh
M	source/blender/blenlib/CMakeLists.txt
A	source/blender/blenlib/intern/serialize.cc
A	source/blender/blenlib/tests/BLI_serialize_test.cc

===================================================================

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> 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
+
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;
+  }
+  

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list