tornavis/source/blender/blenlib/BLI_serialize.hh

316 lines
7.5 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#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.
* - DictionaryValue: 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`.
*
* \code{.cc}
* JsonFormatter json;
* std::stringstream out;
* IntValue test_value(42);
* json.serialize(out, test_value);
* \endcode
*
* ## Deserializing
*
* \code{.cc}
* std::stringstream is("42");
* JsonFormatter json;
* std::unique_ptr<Value> value = json.deserialize(is);
* \endcode
*
* # 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,
Dictionary,
};
class Value;
class StringValue;
class DictionaryValue;
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, eValueType V, typename ContainerItem = typename Container::value_type>
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>>, 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 concrete 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.
* - `DictionaryValue`: 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;
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 DictionaryValue.
* Will return nullptr when it is a different type.
*/
const DictionaryValue *as_dictionary_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 DictionaryValue store their values in an array.
*/
template<
/** The container type where the elements are stored in. */
typename Container,
/** ValueType representing the value (object/array). */
eValueType V,
/** Type of the data inside the container. */
typename ContainerItem>
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 DictionaryValue.
*
* The elements are stored as an key value pair. The value is a shared pointer so it can be shared
* when using `DictionaryValue::create_lookup`.
*/
using DictionaryElementType = 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 DictionaryValue
: public ContainerValue<Vector<DictionaryElementType>, eValueType::Dictionary> {
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 indentation level to use.
* Typically number of chars. Set to 0 to not use indentation.
*/
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