Formats and conversion

Convert between JSON, DUST, EDN and TRON with the library's parse and serialize operations, through the C ABI or any binding. Serialization writes a document as text, or as binary bytes for TRON. The example parses the people document and serializes it in the same format.

Example

Serialize the people document

people_text holds this document:

edn287 B
{:#_ada {:name "Ada"
         :role "engineer"
         :age  36}
 :#_bob {:name   "Bob"
         :role   "engineer"
         :age    41
         :mentor {:#link "#_ada"}}
 :#_cy  {:name "Cy"
         :role "designer"
         :age  29}
 :#_tx  {:author "smoke"
         :reason "seed"}}
python156 B
async with (await stardust.parse(people_text, "edn")) as people:
    output = (await people.serialize("edn"))
result = (await stardust.parse(output, "edn"))
Result / 384 B
{:value {:#_ada {:name "Ada"
                 :role "engineer"
                 :age  36}
         :#_bob {:name   "Bob"
                 :role   "engineer"
                 :age    41
                 :mentor {:#link "#_ada"}}
         :#_cy  {:name "Cy"
                 :role "designer"
                 :age  29}
         :#_tx  {:author "smoke"
                 :reason "seed"}}}

Choosing a format

FormatUse it to
DUSTWrite readable document examples and query expressions.
JSONExchange documents with JSON readers.
EDNExchange values with Clojure readers, under the EDN mapping rules.
TRONEncode a document as binary bytes.

Warning

A format can change number spelling, member order, or tagged values. Check the fidelity table before you depend on them.

EDN query variables

EDN symbols such as ?name parse as native strings. Serialization writes a native string such as "?name" as the EDN symbol ?name. The string must start with ?, contain a name, and form one valid EDN symbol token. Other string values remain quoted.

Query variables therefore keep their symbol form through EDN, JSON, DUST and TRON conversions. Clojure users can quote a query map to pass its symbols as data:

clojure204 B
(with-open [query (sd/from-value
                   '{:find [?name ?age]
                     :where [[?e :name ?name] [?e :age ?age]]
                     :orderBy [?name]})]
  (sd/serialize query :edn))

DUST layout

An object on one line, {op a args {t 1} e 1}, separates its members with whitespace. A top-level document, and any object that spans lines, holds exactly one member per line, and the rest of the line is the value of that member:

text34 B
{name Ada Lovelace
 role engineer}

This object is {"name":"Ada Lovelace","role":"engineer"}. In a multi-line object, {op a args 1 is the member op with the string a args 1. A line that cannot be one bare value, such as {op a args {t 1}, fails with Members_On_One_Line and the offset of its second key. Start each member on its own line, or keep the whole object on one line. Serialization never writes several members on one line of a multi-line object.

Operations

OperationReturnsSummary
Document.serializetextdocument.serialize(format, width) writes text, or binary bytes for TRON.
converttextconvert(text, from, to, width) calls parse and serialize, then releases the intermediate document.

Document.serialize

python63 B
def serialize(self, format: str, width: int = 0) -> str | bytes

Parameters

ParameterTypeOwnershipDefaultMeaning
selfDocumentBorrowed for the callRequiredThe document owner for this operation. Keep it alive during the call.
formatstrValueRequiredThe codec to use for this operation.
widthintValue0The requested formatter line width.

Errors

StatusWhen
"invalid_argument"the format is unknown or the width is negative
"closed"the document is closed
"out_of_memory"native memory is exhausted

Examples

document/serialize

expression_text holds this document:

edn10 B
["+" ?x 1]
python121 B
async with (await stardust.parse(expression_text, "edn")) as expression:
    result = (await expression.serialize("edn"))
Result / 31 B
{:value {:text "[\"+\" ?x 1]"}}

document/serialize

number_text holds this document:

edn1 B
1
python110 B
async with (await stardust.parse(number_text, "edn")) as number:
    result = (await number.serialize("tron"))
Result / 125 B
{:value {:text "TRON\u0002\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0000\u0000\u0000\u0000"}}

document/serialize

name_text holds this document:

edn5 B
"Ada"
python104 B
async with (await stardust.parse(name_text, "edn")) as name:
    result = (await name.serialize("tron"))
Result / 75 B
{:value {:text "TRON<Ada\u0004\u0000\u0000\u0000\u0000\u0000\u0000\u0000"}}

document/serialize

expression_text holds this document:

edn10 B
["+" ?x 1]
python122 B
async with (await stardust.parse(expression_text, "edn")) as expression:
    result = (await expression.serialize("tron"))
Result / 257 B
{:value {:text "TRON\u001c+,?x\u0002\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000e\u0015\u0000\u0007\u0000\u0003\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0006\u0000\u0000\u0000\t\u0000\u0000\u0000\u0012\u0000\u0000\u0000\u0000\u0000\u0000\u0000"}}

document/serialize

expression_text holds this document:

edn10 B
["+" ?x 1]
python121 B
async with (await stardust.parse(expression_text, "edn")) as expression:
    result = (await expression.serialize("edn"))
Result / 31 B
{:value {:text "[\"+\" ?x 1]"}}

C exports: stardust_serialize, stardust_text_data, stardust_text_error, stardust_text_release

convert

python132 B
def convert(
    text: str | bytes, from_format: str, to_format: str, width: int = 0, *, library: str | None = None
) -> str | bytes

Parameters

ParameterTypeOwnershipDefaultMeaning
textstr | bytesValueRequiredEncoded input bytes in the selected format. TRON is binary.
from_formatstrValueRequiredThe input codec.
to_formatstrValueRequiredThe output codec.
widthintValue0The requested formatter line width.
librarystr | NoneValueNoneLoaded native library used by this call.

Errors

StatusWhen
"invalid_json"JSON text is malformed
"invalid_argument"the text is malformed, a format is unknown or the width is negative
"out_of_memory"native memory is exhausted

Examples

convert

expression_text holds these bytes:

text278 B
// One expression built once and evaluated against several inputs. The
// evaluate examples load this file, build `expression`, evaluate it with each
// of `inputs` in order and compare each result with `expected`.
expression [+ ?x 1]
inputs     [{x 1} {x 41}]
expected   [2 42]
python66 B
result = (await stardust.convert(expression_text, "dust", "tron"))
Result / 652 B
{:value {:bytes {:#b64 "VFJPThwrLD94AgEAAAAAAAAADhUABwADAAAABAAAAAYAAAAJAAAAHHgCAQAAAAAAAAAPCicAAAApAAAAHHgCKQAAAAAAAAAPCjwAAAA+AAAADhEAAwACAAAAMgAAAEcAAAACAgAAAAAAAAACKgAAAAAAAAAOEQADAAIAAABiAAAAawAAAIwkY29tbWVudBTNT25lIGV4cHJlc3Npb24gYnVpbHQgb25jZSBhbmQgZXZhbHVhdGVkIGFnYWluc3Qgc2V2ZXJhbCBpbnB1dHMuIFRoZQpldmFsdWF0ZSBleGFtcGxlcyBsb2FkIHRoaXMgZmlsZSwgYnVpbGQgYGV4cHJlc3Npb25gLCBldmFsdWF0ZSBpdCB3aXRoIGVhY2gKb2YgYGlucHV0c2AgaW4gb3JkZXIgYW5kIGNvbXBhcmUgZWFjaCByZXN1bHQgd2l0aCBgZXhwZWN0ZWRgLg8KhQAAAI4AAABsaW5wdXRzDwpnAQAAUQAAAIxleHBlY3RlZA8KeAEAAHQAAAAHDgIBAABuAQAAgQEAAAcKAQAAAIsBAACsZXhwcmVzc2lvbg8KowEAABIAAAAHEkAJAABdAQAAmQEAAK4BAAC4AQAAAAAAAA=="}}}

convert

edn_text holds this document:

edn13 B
"{:a 1 :a 2}"
python227 B
async with (await stardust.parse(edn_text, "edn")) as edn:
    output = (await edn.read((await edn.root()))).text
try:
    (await stardust.parse(output, "edn")).close()
except stardust.StardustError as error:
    result = error
Result / 216 B
{:value {:code    "invalid_edn"
         :message "invalid EDN (Duplicate_Key) at offset 6"
         :path    nil
         :field   nil
         :entity  nil
         :offset  6
         :status  "invalid_argument"}}

convert

instant_text holds this document:

edn28 B
#inst "2026-10-07T12:34:56Z"
python112 B
async with (await stardust.parse(instant_text, "edn")) as instant:
    result = (await instant.serialize("edn"))
Result / 49 B
{:value {:text "#inst \"2026-10-07T12:34:56Z\""}}

convert

container_text holds this document:

edn5 B
[1.0]
python167 B
async with (await stardust.parse(container_text, "edn")) as container:
    output = (await container.serialize("tron"))
result = (await stardust.parse(output, "tron"))
Result / 12 B
{:value [1]}

convert

order_text holds this document:

edn12 B
{:z 2
 :a 1}
python155 B
async with (await stardust.parse(order_text, "edn")) as order:
    output = (await order.serialize("tron"))
result = (await stardust.parse(output, "tron"))
Result / 29 B
{:value {:a 1
         :z 2}}

C exports: stardust_parse, stardust_serialize, stardust_text_data, stardust_text_error, stardust_text_release

Fidelity

Each cell shows the result after parsing and serialization in that format. The notes below the table explain each result.

ValueJSONDUSTEDNTRON
null_booleanRound tripRound tripRound tripRound trip
integerRound tripChangedChangedChanged
floatRound tripRound tripRound tripChanged
non_finiteRefusedChangedRefusedRefused
decimal_ratioRound tripRound tripChangedChanged
stringRound tripRound tripChangedRound trip
member_orderRound tripRound tripRound tripChanged
duplicate_keysRound tripRound tripRefusedChanged
empty_objectRound tripRound tripRound tripRound trip
utcRound tripRound tripChangedRound trip
linkRound tripRound tripRound tripRound trip
binaryRound tripRound tripRound tripChanged
edn_formsNot applicableNot applicableChangedNot applicable

Fidelity notes

  • null_boolean
    • JSON: Round trip. Null and booleans keep their values.
    • DUST: Round trip. Null and booleans keep their values.
    • EDN: Round trip. EDN nil maps to null.
    • TRON: Round trip. Binary tags preserve null and booleans.
  • integer
    • JSON: Round trip. The codec preserves integer spelling.
    • DUST: Changed. Leading-zero tokens are strings.
    • EDN: Changed. Integer spelling is canonical.
    • TRON: Changed. Integers keep their value and use canonical spelling.
  • float
    • JSON: Round trip. The codec preserves float spelling.
    • DUST: Round trip. The codec preserves float spelling.
    • EDN: Round trip. The codec preserves this float spelling.
    • TRON: Changed. Integral floats in containers become integers.
  • non_finite
    • JSON: Refused. JSON has no non-finite numbers.
    • DUST: Changed. NaN is a string.
    • EDN: Refused. The parser rejects non-finite numeric values.
    • TRON: Refused. The parser rejects non-finite binary values.
  • decimal_ratio
    • JSON: Round trip. The codec preserves finite decimal spelling.
    • DUST: Round trip. The codec preserves finite decimal spelling.
    • EDN: Changed. Ratios reduce to native numbers.
    • TRON: Changed. Decimals use f64 precision.
  • string
    • JSON: Round trip. Strings require UTF-8.
    • DUST: Round trip. Number-like strings require quotes.
    • EDN: Changed. A lone surrogate becomes a question mark.
    • TRON: Round trip. Decoded strings require UTF-8.
  • member_order
    • JSON: Round trip. The codec preserves object order.
    • DUST: Round trip. The codec preserves object order.
    • EDN: Round trip. The codec preserves map order.
    • TRON: Changed. Members use trie order.
  • duplicate_keys
    • JSON: Round trip. The codec preserves all duplicate members.
    • DUST: Round trip. The codec preserves all duplicate members.
    • EDN: Refused. The parser rejects equal map keys.
    • TRON: Changed. The codec keeps the last duplicate value.
  • empty_object
    • JSON: Round trip. The empty object is braces.
    • DUST: Round trip. The empty root object serializes to zero bytes.
    • EDN: Round trip. The empty map is braces.
    • TRON: Round trip. The empty object has a binary footer.
  • utc
    • JSON: Round trip. UTC remains a tagged object.
    • DUST: Round trip. UTC uses a tagged object.
    • EDN: Changed. Millisecond instants use the EDN inst tag.
    • TRON: Round trip. UTC remains a plain map.
  • link
    • JSON: Round trip. Links remain tagged objects.
    • DUST: Round trip. Links remain tagged objects.
    • EDN: Round trip. Links remain maps.
    • TRON: Round trip. Links remain maps.
  • binary
    • JSON: Round trip. Base64 remains a tagged object.
    • DUST: Round trip. Base64 remains a tagged object.
    • EDN: Round trip. Base64 remains a map.
    • TRON: Changed. Valid padded base64 in a sole member becomes a binary node.
  • edn_forms
    • JSON: Not applicable. EDN forms have no JSON syntax.
    • DUST: Not applicable. EDN forms have no DUST syntax.
    • EDN: Changed. The parser converts keywords, symbols, characters, sets and lists to native values. It omits metadata and discarded forms. It reads only the first form.
    • TRON: Not applicable. EDN forms have no TRON syntax.

The host mapping on the Language values and documents page shows how language values become documents without a text format.