Structured Codec
structured@1.0
turns complex objects into strings with extended ao-types
according to the HTTP Structured Field Values (RFC-9651) specification.
This object type is called TABM (Type Annotated Binary Message)
in HyperBEAM.
ao-types
has the following types:
integer
:123
float
:3.14
binary
:"abc"
|Buffer.from([1,2,3])
atom
:true
|false
|null
|Symbol("abc")
list
:[1, 2, 3]
empty-binary
:""
|Buffer.from([])
empty-list
:[]
empty-message
:{}
/HyperBEAM/src/dev_mydev.erl
-export([ structured_to/3, structured_from/3 ]).
structured_to(Msg1, Msg2, Opts) ->
Body = maps:get(<<"body">>, Msg1),
OBJ = dev_codec_json:from(Body),
TABM = dev_codec_structured:to(OBJ),
JSON = dev_codec_json:to(TABM),
{ok, JSON}.
structured_from(Msg1, Msg2, Opts) ->
Body = maps:get(<<"body">>, Msg1),
TABM = dev_codec_json:from(Body),
OBJ = dev_codec_structured:from(TABM),
JSON = dev_codec_json:to(OBJ),
{ok, JSON}.
Encode JSON with dev_codec_structured:from
.
/test/codec-structured.test.js
const cases = [
{ list: [1, true, "abc"] },
{ nested_list: [1, [2, 3]] },
{ a: { b: [1, 2, 3] } },
{ a: [1, 2], b: [3, 4] },
{ empty_list: [], empty_binary: "", empty_message: {} },
]
for (const v of cases) {
const { out } = await hb.post({
path: "/~mydev@1.0/structured_from",
body: JSON.stringify(v),
})
console.log(JSON.parse(out))
}
{ list: [1, true, "abc"] }
{
'ao-types': 'list="list"',
list: '"(ao-type-integer) 1", "(ao-type-atom) \\"true\\"", "abc"'
}
{ nested_list: [1, [2, 3]] }
{
'ao-types': 'nested_list="list"',
nested_list: '"(ao-type-integer) 1", "(ao-type-list) \\"(ao-type-integer) 2\\", \\"(ao-type-integer) 3\\""'
}
{ a: { b: [1, 2, 3] } }
{
a: {
'ao-types': 'b="list"',
b: '"(ao-type-integer) 1", "(ao-type-integer) 2", "(ao-type-integer) 3"'
}
}
{ a: [1, 2], b: [3, 4] }
{
a: '"(ao-type-integer) 1", "(ao-type-integer) 2"',
'ao-types': 'a="list", b="list"',
b: '"(ao-type-integer) 3", "(ao-type-integer) 4"'
}
{ empty_list: [], empty_binary: "", empty_message: {} }
{
'ao-types': 'empty_binary="empty-binary", empty_list="empty-list", empty_message="empty-message"'
}
You can specify ao-types
of the values at the same level, annotate keys with (ao-type-[type])
, and join multiple entries with ,
.
Let's decode the encoded values.
/test/codec-structured.test.js
const cases = [
{
'ao-types': 'list="list"',
list: '"(ao-type-integer) 1", "(ao-type-atom) \\"true\\"", "abc"'
},
{
'ao-types': 'nested_list="list"',
nested_list: '"(ao-type-integer) 1", "(ao-type-list) \\"(ao-type-integer) 2\\", \\"(ao-type-integer) 3\\""'
},
{
a: {
'ao-types': 'b="list"',
b: '"(ao-type-integer) 1", "(ao-type-integer) 2", "(ao-type-integer) 3"'
}
},
{
a: '"(ao-type-integer) 1", "(ao-type-integer) 2"',
'ao-types': 'a="list", b="list"',
b: '"(ao-type-integer) 3", "(ao-type-integer) 4"'
},
{
'ao-types': 'empty_binary="empty-binary", empty_list="empty-list", empty_message="empty-message"'
}
]
for (const v of cases) {
const { out } = await hb.post({
path: "/~mydev@1.0/structured_to",
body: JSON.stringify(v),
})
console.log(JSON.parse(out))
}
Running Tests
You can find the working test file for this chapter here:
Run tests:
Terminal
yarn test test/codec-structured.test.js