DynaBuffers is a cross platform serialization library architected for dynamic serialization while taking maximum memory efficiency into account. It allows you to directly serialize from map and deserializing into map structures while still supporting forwards/backwards compatibility.
- Java
- Python
- Javascript (in progress)
- Typescript (in progress)
- Ease of integration - DynaBuffers don't use auto generated classes but instead creates the serializer/deserializer logic on the fly.
- Memory efficiency and speed - Only the information described in the schema definition gets stored in the byte buffer. No additional data is allocated.
- Schema evolution - DynaBuffers supports schema evolution by defining class attributes as deprecated and/or optional.
- No code footprint - No generated code is needed.
In order to generate the ANTLR sources invoke the gradle tasks generateGrammarSource and generateGrammarSourcePython.
DynaBuffers supports the top level entities class, enum and union.
Classes are the date transfer objects whose data are described by fields. Fields must have a name and a datatype and can have options. DynaBuffers supports the following datatypes:
- string
- byte
- short
- int
- long
- float
- boolean
- array
- option
- map
- enums
- unions
- other classes
class Color { name:string }
class Image {
type:string
size:short
data:[byte]
}
class Product(primary) {
name:string
price:float
image:Image
color:Color
}
Classes and fields can be deprecated and optional. Optional fields are defined by defining a default value.
class Color(deprecated) {
name:string = "red"
rgb:string(deprecated)
}
When multiple classes are defined DynaBuffers need to know which class is the primary class.
class A(primary) {
content:string
classB:B
classC:C
}
class B {
content:string
}
class C {
content:string
}
Dynabuffers also supports scalar value serialization by using an implicit class. An Implicit class adds the map wrapper automatically around the scalar value.
class Data(implicit) {
value:[byte]
}
engine.serialize("text".getBytes())
As default, the deserialization method returns a DynabuffersMap instance which has access to the schema and validates each access on the map.
class Data {
strVal:string
intVal:int
}
val map = engine.deserialize(engine.serialize({"strVal":"text", "intVal":0))
map.get("strVal") // returns "text"
map.get("intVal") // returns 0
map.getString("strVal") // returns "text"
map.getInt("intVal") // returns 0
map.get("unknown") // throws an exception
map.getInt("strVal") // throws an exception
In the case of an implicit class deserialization, the return value of the deserialize funciton is an ImplicitDynabuffersMap instance. This implementation extends DynabuffersMap and has an additional function getValue(), which returns the scalar value.
Enums are enumerations of static values.
enum Color { RED GREEN BLUE }
class Product {
name:string
color:Color
}
Unions can be used to group multiple classes.
WARNING: The order of classes in a union should not be changed. New classes should always be appended at the end!
class MessageA { type:string }
class MessageB { type:string }
class MessageC { type:string }
union Message {
MessageA
MessageB
MessageC
}
class Request {
type:Message
}
By using the field :type as a type hint it is possible to define which union class type to use. The :type field refers to the union class position e.g. 0 for the first element and so on. Alternatively, the class name can also be used as an alias for the :type field.
class MessageA { content:string }
class MessageB { content:string }
union Message {
MessageA
MessageB
}
engine.serialize({"content":"text", ":type":0)
engine.serialize({"content":"text", ":type":"MessageA")
Union types can also be declared as primary or implicit.
Namespaces can be used to group class/union/enum types to logical groups.
namespace request {
class Data { content: string }
}
namespace response {
class Data { content: string }
}
val bytes = engine.serialize(mapOf("content" to "text"),"request")
//or
val bytes = engine.serialize(mapOf("content" to "text", ":namespace" to "request"))
engine.deserialize(bytes) // => mapOf("content" to "text", ":namespace" to "request")
Please note that namespaces can be nested in other namespaces
namespace abc{
class DataLevel0 {
value0: string
}
namespace def {
class DataLevel1 {
value1: int
}
namespace ghi {
class DataLevel2 {
value2: float
}
}
}
}
#Serializing DataLevel2
engine.serialize(
mapOf("value2" to 0.2f),
listOf("abc", "def", "ghi")
)
#Serializing DataLevel1
engine.serialize(
mapOf("value1" to 3),
listOf("abc", "def")
)
#Serializing DataLevel0
engine.serialize(
mapOf("value0" to "someString"),
listOf("abc")
)
By using annotations it is possible to declare validation rules for class fields. Dynabuffers has the following built-in annotations:
name | description | attributes | datatype |
---|---|---|---|
GreaterThan | Target value must be greater than the configured value | size:int | int & float |
LowerThan | Target value must be lower than the configured value | size:int | int & float |
GreaterEquals | Target value must be greater equals the configured value | size:int | int & float |
LowerEquals | Target value must be lower equals the configured value | size:int | int & float |
MaxLength | Target value length must be lower equals the configured value | size:int | string |
MinLength | Target value length must be greater equals the configured value | size:int | string |
NotBlank | Target must not be blank | none | string |
class Product {
@NotBlank
@MinLength(3)
@MaxLength(10)
name:string
@GreaterThan(0)
price:float
}
val engine = Dynabuffers.parse("class Color { name:string }")
val bytes = engine.serialize(mapOf("name" to "red"))
val map = engine.deserialize(bytes)
engine = Dynabuffers.parse("class Color { name:string }")
bytes = engine.serialize({"name" : "red"})
map = engine.deserialize(bytes)
Releases are triggered locally. Just a tag will be pushed and CI takes care of the rest.
Run ./gradlew final -x sendReleaseEmail -Prelease.scope=major
locally.
Run ./gradlew final -x sendReleaseEmail -Prelease.scope=minor
locally.
Must be released from branch (e.g. release/1.0.x
)
Run ./gradlew final -x sendReleaseEmail -Prelease.scope=patch
locally.