This library extends js-xdr to let you convert XDR to JSON and JSON to XDR.
via npm:
npm install --save json-xdr
The examples below will use the following XDR definition:
const XDR = require('js-xdr')
const types = XDR.config((xdr) => {
xdr.typedef("Hash", xdr.opaque(2));
xdr.typedef('Int32', xdr.int());
xdr.struct("Price", [
["n", xdr.lookup("Int32")],
["d", xdr.lookup("Int32")],
]);
xdr.enum("MemoType", {
memoNone: 0,
memoText: 1,
memoId: 2,
memoHash: 3,
memoReturn: 4,
});
xdr.union("Memo", {
switchOn: xdr.lookup("MemoType"),
switchName: "type",
switches: [
["memoNone", xdr.void()],
["memoText", "text"],
["memoId", "id"]
],
arms: {
text: xdr.string(28),
id: xdr.lookup("Int32")
},
});
xdr.typedef('CounterInt', xdr.option(xdr.int()));
xdr.struct('Event', [
["attendees", xdr.int()],
["eventName", xdr.string(50)],
["secretSpeakers", xdr.array(xdr.lookup("Hash"), 2)],
["speakers", xdr.varArray(xdr.string())],
["price", xdr.lookup("Price")],
["memo", xdr.lookup("Memo")],
['meta', xdr.lookup('TransactionMeta')],
['counter', xdr.lookup("CounterInt")]
])
xdr.enum("TransactionMetaType", {
none: 0,
paid: 1
});
xdr.union("TransactionMeta", {
switchOn: xdr.lookup("TransactionMetaType"),
switches: [
["none", xdr.void()],
["paid", "price"]
],
arms: {
price: xdr.lookup("Price")
},
defaultArm: xdr.void()
});
})
You can convert an XDR struct to JSON using the function toJSON
.
import { toJSON } from 'json-xdr';
let event = new types.Event({
attendees: 5,
eventName: "Lumenauts get together",
secretSpeakers: [Buffer.from([0, 0]), Buffer.from([0, 1])],
speakers: ['Jed', 'Tom', 'Zac'],
price: new types.Price({
n: 2,
d: 1
}),
memo: types.Memo.memoText("foo"),
meta: types.TransactionMeta.paid(new types.Price({
n: 2,
d: 1
})),
counter: 2
})
let payload = toJSON(event);
console.log(payload)
// Output
// {
// "attendees": 5,
// "eventName": "Lumenauts get together",
// "secretSpeakers": [
// "AAA=",
// "AAE="
// ],
// "speakers": [
// "Jed",
// "Tom",
// "Zac"
// ],
// "price": {
// "n": 2,
// "d": 1
// },
// "memo": {
// "_type": "memoText",
// "text": "foo"
// },
// "meta": {
// "_type": "paid",
// "price": {
// "n": 2,
// "d": 1
// }
// },
// "counter": 2
// }
Given a JSON object representing a struct from your types definition, you can convert it to XDR using the function toXDR
.
import { toXDR } from 'json-xdr';
let payload = {
"attendees": 5,
"eventName": "Lumenauts get together",
"secretSpeakers": [
"AAA=",
"AAE="
],
"speakers": [
"Jed",
"Tom",
"Zac"
],
"price": {
"n": 2,
"d": 1
},
"memo": {
"_type": "memoText",
"text": "foo"
},
"meta": {
"_type": "paid",
"price": {
"n": 2,
"d": 1
}
},
"counter": 2
}
let event = toXDR(types.Event, payload)
assert.ok(xdrEvent instanceof types.Event)
JavaScript native types are used when possible, for example, String
,
Integer
, Array
and null
. However there are some types which
don't have an equivalent representation. For those types the
serialization and deserialization is done using the following rules.
Opaque and Variable Opaque data are represented in js-xdr
using a binary data buffer. Buffers are serializer as a base64
encoded string.
For the following definition:
const types = XDR.config((xdr) => {
xdr.struct('withOpaque', [
['opaque', xdr.opaque(3)],
['varOpaque', xdr.varOpaque(2)]
])
})
let withOpaque = new types.withOpaque({
opaque: Buffer.from([0, 0, 1]),
varOpaque: Buffer.from([0, 1])
})
Calling #toJSON
will result in:
{
opaque: 'AAAB',
varOpaque: 'AAE='
}
Array
and VarArray
are serialized as a JavaScript Arrays and then for each element we
apply the serialization rules defined in this library.
For the following definition:
const types = XDR.config((xdr) => {
xdr.typedef("Hash", xdr.opaque(2));
xdr.struct('Event', [
["attendees", xdr.int()],
["eventName", xdr.string(50)],
["secretSpeakers", xdr.array(xdr.lookup("Hash"), 2)],
["speakers", xdr.varArray(xdr.string())]
])
})
let event = new types.Event({
attendees: 5,
eventName: "Lumenauts get together",
secretSpeakers: [Buffer.from([0, 0]), Buffer.from([0, 1])],
speakers: ['Jed', 'Tom', 'Zac']
})
Calling #toJSON
will result in:
{
attendees: 5,
eventName: 'Lumenauts get together',
secretSpeakers: [ 'AAA=', 'AAE=' ],
speakers: [ 'Jed', 'Tom', 'Zac' ]
}
Notice how speakers
get serialized as a JavaScript String
while secretSpeakers
which is an Opaque
, get serialized as base64, which is documented above.
Unions are serialized as a JavaScript object, the discriminant is
store in the property _type
. If there is an arm for the given
discriminant, then a property with the arm's name is set in the object
and its value should contain a type as defined in the XDR declaration.
The following struct has two properties of type union: memo
and meta
.
let event = new types.Event({
attendees: 5,
eventName: "Lumenauts get together",
secretSpeakers: [Buffer.from([0, 0]), Buffer.from([0, 1])],
speakers: ['Jed', 'Tom', 'Zac'],
price: new types.Price({
n: 2,
d: 1
}),
memo: types.Memo.memoText("foo"),
meta: types.TransactionMeta.paid(new types.Price({
n: 2,
d: 1
})),
counter: 2
})
The code below shows the result after calling toJSON(event)
, notice how the memo
property has an object
with the key _type: "memoText"
and a property matching the arm declaration text
with a string value of foo
.
Similarly, the property meta
has an object with _type: "paid"
, with the arm price
which contains a Struct of type Price
.
{
...,
"memo": {
"_type": "memoText",
"text": "foo"
},
"meta": {
"_type": "paid",
"price": {
"n": 2,
"d": 1
}
}
}
Given the following definition:
xdr.union("TransactionMeta", {
switchOn: xdr.lookup("TransactionMetaType"),
switches: [
["none", xdr.void()],
["paid", "price"]
],
arms: {
price: xdr.lookup("Price")
},
defaultArm: xdr.string(3)
});
If the union instance uses the default arm, it will be serialized like the following:
{
...,
"meta": {
"_type": "pending",
"default": "foo"
}
}
If there is no arm for the discriminant, only the _type
property will appear on the object.
{
...,
"meta": {
"_type": "withVoidArm",
}
}
You can find more examples in the test here.