christiangeorgelucas/ion-tools
v0.1.0GoThe ion-tools package provides composable Axiom nodes for working with Amazon Ion, a self-describing data format used by various AWS services. It facilitates operations such as transcoding between text and binary formats, converting Ion to JSON (and vice versa), validating Ion documents, and inspecting their structure, making it essential for developers working with Ion data in Go.
Use cases
- •Convert Ion documents to and from JSON for interoperability
- •Validate the structure of Ion documents before processing
- •Inspect and analyze the types and annotations in Ion data
- •Pretty-print Ion documents for better readability
- •Extract symbol tables from Ion documents for efficient encoding
Nodes (8)
The BinaryToText node converts an Ion document from its compact binary encoding to a human-readable text encoding, preserving all top-level values, containers, field names, and annotations. It serves as the inverse operation of the TextToBinary node.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Convert an Ion document from its compact binary encoding to its text
// encoding. Every top-level value, container, field name, and annotation in
// the input is preserved exactly — only the byte-level encoding changes.
func BinaryToText(ctx context.Context, ax axiom.Context, input *gen.IonBinary) (*gen.IonText, error) {
if input == nil || len(input.Data) == 0 {
return &gen.IonText{Error: "data is required"}, nil
}
text, err := ionBinaryToText(input.Data)
if err != nil {
return &gen.IonText{Error: err.Error()}, nil
}
return &gen.IonText{Text: text}, nil
}
The ExtractSymbolTable node processes an Ion document to extract its local symbol table, which maps small integer symbol IDs to their corresponding text. It validates the document to ensure that any malformed Ion is reported as an error, providing a reliable symbol table extraction.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Extract the local symbol table an Ion document (text or binary,
// auto-detected) carries — the table mapping small integer symbol IDs to
// their text, which is what lets Ion binary encode field names, annotation
// names, and symbol values compactly. Most informative for Ion binary
// input, which always carries one; plain Ion text generally returns an
// empty result unless it explicitly declares a $ion_symbol_table struct —
// that is expected behavior, not an error.
func ExtractSymbolTable(ctx context.Context, ax axiom.Context, input *gen.IonInput) (*gen.SymbolTableInfo, error) {
if input == nil || len(input.Data) == 0 {
return &gen.SymbolTableInfo{Error: "data is required"}, nil
}
info, err := extractSymbolTableFromIon(input.Data)
if err != nil {
return &gen.SymbolTableInfo{Error: err.Error()}, nil
}
return info, nil
}
The InspectStructure node analyzes an Ion document, providing a detailed structural summary that includes a type tree for each top-level value, distinct Ion types and annotations, maximum container nesting depth, and total value count. This node highlights the unique features of Ion, such as type annotations and a richer type system, distinguishing it from traditional JSON inspection.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Produce a structural, type-aware summary of an Ion document (text or
// binary, auto-detected): a type tree for each top-level value, the
// distinct Ion types and annotations used anywhere in the document, the
// maximum container nesting depth, and the total value count. This
// surfaces what makes Ion inspection different from inspecting JSON — Ion
// values can carry type annotations (e.g. `meters::5`) and a richer type
// system (timestamp, decimal, symbol, blob, clob, sexp) that this node
// reports explicitly rather than collapsing into a generic "object"/"array".
func InspectStructure(ctx context.Context, ax axiom.Context, input *gen.IonInput) (*gen.StructureReport, error) {
if input == nil || len(input.Data) == 0 {
return &gen.StructureReport{Error: "data is required"}, nil
}
report, err := inspectIon(input.Data)
if err != nil {
return &gen.StructureReport{Error: err.Error()}, nil
}
return report, nil
}
The IonToJson node converts Ion documents, whether text or binary, into JSON format while intentionally losing some data fidelity due to JSON's simpler type system. It handles various Ion types by transforming them into JSON-compatible representations, ensuring that the output is structured correctly based on the number of top-level Ion values.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Convert an Ion document (text or binary, auto-detected) to JSON text. This
// is intentionally LOSSY: annotations are dropped, symbols become strings,
// decimals and timestamps become JSON strings (not numbers) holding their
// exact Ion text form, and blobs/clobs become base64 strings — see the
// ionutil.go helper's doc comment for the full, honest list of what does not
// survive. If the document has exactly one top-level Ion value, its direct
// JSON translation is returned; otherwise (zero or multiple top-level
// values) the result is a JSON array of them, in document order.
func IonToJson(ctx context.Context, ax axiom.Context, input *gen.IonInput) (*gen.JsonDoc, error) {
if input == nil || len(input.Data) == 0 {
return &gen.JsonDoc{Error: "data is required"}, nil
}
j, err := ionToJSON(input.Data)
if err != nil {
return &gen.JsonDoc{Error: err.Error()}, nil
}
return &gen.JsonDoc{Json: j}, nil
}
The JsonToIon node converts a JSON document into Ion text format, ensuring that the structure and data types are preserved according to Ion specifications. It handles JSON's null, boolean, number, string, array, and object types, while maintaining the order of fields and managing integer overflows appropriately.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Convert a JSON document to Ion text. JSON's null/bool/number/string/
// array/object map onto Ion's null/bool/int-or-float/string/list/struct,
// preserving field order. JSON has no annotation, symbol, decimal,
// timestamp, blob, or clob concept, so this never produces one — chain with
// TextToBinary if you need Ion binary output instead of text.
func JsonToIon(ctx context.Context, ax axiom.Context, input *gen.JsonDoc) (*gen.IonText, error) {
if input == nil || input.Json == "" {
return &gen.IonText{Error: "json is required"}, nil
}
text, err := jsonToIonText(input.Json)
if err != nil {
return &gen.IonText{Error: err.Error()}, nil
}
return &gen.IonText{Text: text}, nil
}
The PrettyPrint node re-serializes an Ion document, whether in text or binary format, into a human-readable Ion text format with proper indentation and line breaks for better readability. It ensures that each value or field is displayed on a separate line, making it easier to navigate nested structures.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Re-serialize an Ion document (text or binary, auto-detected) as indented,
// human-readable Ion text — one value/field per line with nested containers
// indented, unlike the compact single-line form TextToBinary's inverse
// (BinaryToText) produces.
func PrettyPrint(ctx context.Context, ax axiom.Context, input *gen.IonInput) (*gen.IonText, error) {
if input == nil || len(input.Data) == 0 {
return &gen.IonText{Error: "data is required"}, nil
}
text, err := ionPrettyPrint(input.Data)
if err != nil {
return &gen.IonText{Error: err.Error()}, nil
}
return &gen.IonText{Text: text}, nil
}
The TextToBinary node converts an Ion document from its human-readable text encoding to its compact binary encoding, preserving all top-level values, containers, field names, and annotations without any data alteration. This process results in a byte-level re-encoding that begins with the version marker 0xE0 0x01 0x00 0xEA.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Convert an Ion document from its text encoding to its compact binary
// encoding (the form starting with the 0xE0 0x01 0x00 0xEA version marker).
// Every top-level value, container, field name, and annotation in the input
// is preserved exactly — only the byte-level encoding changes.
func TextToBinary(ctx context.Context, ax axiom.Context, input *gen.IonText) (*gen.IonBinary, error) {
if input == nil || input.Text == "" {
return &gen.IonBinary{Error: "text is required"}, nil
}
data, err := ionTextToBinary(input.Text)
if err != nil {
return &gen.IonBinary{Error: err.Error()}, nil
}
return &gen.IonBinary{Data: data}, nil
}
The ValidateIon node checks the well-formedness of an Ion document, automatically detecting whether it is text or binary. It fully materializes every top-level value and descends into containers to catch any parse errors early in the process.
View source
package nodes
import (
"context"
"christiangeorgelucas/ion-tools/axiom"
gen "christiangeorgelucas/ion-tools/gen"
)
// Check whether an Ion document (text or binary, auto-detected) is
// well-formed. Parses every top-level value fully (including descending
// into every container) so a deferred parse error — a malformed decimal, an
// invalid UTF-8 string, an out-of-range timestamp — is caught here rather
// than surfacing later in a different node. valid is false and error
// explains why on any parse failure; value_count reports how many top-level
// values were successfully read before validation concluded (the full count
// when valid).
func ValidateIon(ctx context.Context, ax axiom.Context, input *gen.IonInput) (*gen.ValidationResult, error) {
if input == nil || len(input.Data) == 0 {
return &gen.ValidationResult{Valid: false, Error: "data is required"}, nil
}
return validateIonData(input.Data), nil
}
Messages (8)
Download .protoThe Ion text output generated from the input JSON, if conversion is successful.
An error message indicating any issues encountered during the conversion process.
An error message indicating any issues encountered during the conversion process.
A string that indicates any errors encountered during the inspection process, such as malformed input.
Use as a tool in any AI agent
christiangeorgelucas/ion-tools is callable as a tool from any MCP client (Claude, Cursor, or your own agent) via Axiom's hosted MCP server — no install, no download. Add the server, then your agent can search and invoke it, or pin it as a typed tool.
claude mcp add --transport http axiom https://api.axiomide.com/mcp --header "Authorization: Bearer YOUR_AXIOM_API_KEY"Replace YOUR_AXIOM_API_KEY with a key you create in Console → API Keys.
MCP server setup & other clients →Use this package
Sign in to import christiangeorgelucas/ion-tools into the graph editor — drop it into a flow and hit run. Or call it directly via the API Reference with your API key.
Sign in to useOpen editor