Report a bug
If you spot a problem with this page, click here to create a GitHub issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

mir.ion.deser.text

Ion Text Deserialization API
Heavily influenced (and compatible) with upstream Ion implementations (compatible with ion-go)
Authors:
Harrison Ford
struct IonTextDeserializer(Serializer);
Deserializer for the Ion Text format
pure @safe this(Serializer* ser);
Constructor
Parameters:
Serializer* ser A pointer to a serializer
pure @safe void opCall(scope const(char)[] text);
This function starts the deserializing process, and attempts to fully read through the text provided until it reaches the end.
Parameters:
const(char)[] text The text to deserialize
T deserializeText(T)(scope const(char)[] text);
Deserialize an Ion Text value to a D value.
Parameters:
const(char)[] text The text to deserialize
Returns:
The deserialized Ion Text value
Examples:
Test struct deserialization
import mir.ion.value;
static struct Book
{
    string title;
    bool wouldRecommend;
    string description;
    uint numberOfNovellas;
    double price;
    float weight;
    string[] tags;
}

static immutable textData = `
{
    "title": "A Hero of Our Time",
    "wouldRecommend": true,
    "description": "",
    "numberOfNovellas": 5,
    "price": 7.99,
    "weight": 6.88,
    "tags": ["russian", "novel", "19th century"]
}`;

Book book = deserializeText!Book(textData);
assert(book.description.length == 0);
assert(book.numberOfNovellas == 5);
assert(book.price == 7.99);
assert(book.tags.length == 3);
assert(book.tags[0] == "russian");
assert(book.tags[1] == "novel");
assert(book.tags[2] == "19th century");
assert(book.title == "A Hero of Our Time");
assert(book.weight == 6.88f);
assert(book.wouldRecommend);
Examples:
Test that strings are being de-serialized properly
import mir.ion.value;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, const(char)[] expected)
{
    const(char)[] output = text2ion!false(ionData).IonValue.describe.get!(const(char)[]);
    assert(output == expected);
}

test(`"hello"`, "hello");
test(`"hello\x20world"`, "hello world");
test(`"hello\u2248world"`, "hello≈world");
test(`"hello\U0001F44Dworld"`, "hello👍world");
Examples:
Test that timestamps are de-serialized properly
import mir.ion.value;
import mir.ion.conv : text2ion;
import mir.timestamp;
void test(const(char)[] ionData, Timestamp expected)
{
    Timestamp t = text2ion!false(ionData).IonValue.describe.get!(IonTimestamp).get;
    assert(expected == t);
}

void testFail(const(char)[] ionData, Timestamp expected)
{
    Timestamp t = text2ion!false(ionData).IonValue.describe.get!(IonTimestamp).get;
    assert(expected != t);
}

test("2001-01T", Timestamp(2001, 1));
test("2001-01-02", Timestamp(2001, 1, 2));
test("2001-01-02T", Timestamp(2001, 1, 2));
test("2001-01-02T03:04", Timestamp(2001, 1, 2, 3, 4));
test("2001-01-02T03:04Z", Timestamp(2001, 1, 2, 3, 4));
test("2001-01-02T03:04+00:00", Timestamp(2001, 1, 2, 3, 4));
test("2001-01-02T03:05+00:01", Timestamp(2001, 1, 2, 3, 4).withOffset(1));
test("2001-01-02T05:05+02:01", Timestamp(2001, 1, 2, 3, 4).withOffset(2*60+1));
test("2001-01-02T03:04:05", Timestamp(2001, 1, 2, 3, 4, 5));
test("2001-01-02T03:04:05Z", Timestamp(2001, 1, 2, 3, 4, 5));
test("2001-01-02T03:04:05+00:00", Timestamp(2001, 1, 2, 3, 4, 5));
test("2001-01-02T03:05:05+00:01", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(1));
test("2001-01-02T05:05:05+02:01", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(2*60+1));
test("2001-01-02T03:04:05.666", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666));
test("2001-01-02T03:04:05.666Z", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666));
test("2001-01-02T03:04:05.666666Z", Timestamp(2001, 1, 2, 3, 4, 5, -6, 666_666));
test("2001-01-02T03:54:05.666+00:50", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666).withOffset(50));
test("2001-01-02T03:54:05.666666+00:50", Timestamp(2001, 1, 2, 3, 4, 5, -6, 666_666).withOffset(50));

testFail("2001-01-02T03:04+00:50", Timestamp(2001, 1, 2, 3, 4));
testFail("2001-01-02T03:04:05+00:50", Timestamp(2001, 1, 2, 3, 4, 5));
testFail("2001-01-02T03:04:05.666Z", Timestamp(2001, 1, 2, 3, 4, 5));
testFail("2001-01-02T03:54:05.666+00:50", Timestamp(2001, 1, 2, 3, 4, 5));
Examples:
Test that binary literals are de-serialized properly.
import mir.ion.value;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, uint val)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonUInt);
    assert(v.get!uint == val);
}

// Disabled until implemented in Mir
/*
test("0b10101", 0b10101);
test("0b11111", 0b11111);
test("0b111111111111111111111", 0b1111_1111_1111_1111_1111_1);
*/
Examples:
Test that signed / unsigned integers are de-serialized properly.
import mir.ion.value;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, ulong val)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonUInt);
    assert(v.get!ulong == val);
}

void testNeg(const(char)[] ionData, ulong val)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonNInt);
    assert(v.get!long == -val);
}

test("0xabc_def", 0xabc_def);
test("0xabcdef", 0xabcdef);
test("0xDEADBEEF", 0xDEADBEEF);
test("0xDEADBEEF", 0xDEAD_BEEF);
test("0xDEAD_BEEF", 0xDEAD_BEEF);
test("0xDEAD_BEEF", 0xDEADBEEF);
test("0x0123456789", 0x0123456789);
test("0x0123456789abcdef", 0x0123456789abcdef);
test("0x0123_4567_89ab_cdef", 0x0123_4567_89ab_cdef);

testNeg("-0xabc_def", 0xabc_def);
testNeg("-0xabc_def", 0xabc_def);
testNeg("-0xabcdef", 0xabcdef);
testNeg("-0xDEADBEEF", 0xDEADBEEF);
testNeg("-0xDEADBEEF", 0xDEAD_BEEF);
testNeg("-0xDEAD_BEEF", 0xDEAD_BEEF);
testNeg("-0xDEAD_BEEF", 0xDEADBEEF);
testNeg("-0x0123456789", 0x0123456789);
testNeg("-0x0123456789abcdef", 0x0123456789abcdef);
testNeg("-0x0123_4567_89ab_cdef", 0x0123_4567_89ab_cdef);
Examples:
Test that infinity & negative infinity are deserialized properly.
import mir.ion.value;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, float expected)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonFloat);
    assert(v.get!float == expected);
}

test("-inf", -float.infinity);
test("+inf", float.infinity);
Examples:
Test that NaN is deserialized properly.
import mir.ion.value;
import mir.ion.conv : text2ion;
alias isNaN = x => x != x;
void test(const(char)[] ionData)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonFloat);
    assert(isNaN(v.get!float));
}

test("nan");
Examples:
Test that signed / unsigned integers and decimals and floats are all deserialized properly.
import mir.ion.value;
import mir.ion.conv : text2ion;
void test_uint(const(char)[] ionData, ulong expected)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonUInt);
    assert(v == expected);
}

void test_nint(const(char)[] ionData, long expected)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonNInt);
    assert(v == expected);
}

void test_dec(const(char)[] ionData, double expected)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonDecimal);
    assert(v.get!double == expected);
}

void test_float(const(char)[] ionData, float expected)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonFloat);
    assert(v.get!float == expected);
}

test_uint("123", 123);
test_nint("-123", -123);
test_dec("123.123123", 123.123123);
test_dec("123.123123", 123.123123);
test_dec("123.123123d0", 123.123123);
test_dec("123.123123d0", 123.123123);
test_dec("-123.123123", -123.123123);
test_dec("-123.123123d0", -123.123123);
test_dec("18446744073709551615.", 1844_6744_0737_0955_1615.0);
test_dec("-18446744073709551615.", -1844_6744_0737_0955_1615.0);
test_dec("18446744073709551616.", 1844_6744_0737_0955_1616.0);
test_dec("-18446744073709551616.", -1844_6744_0737_0955_1616.0);
test_float("123.456789e-6", 123.456789e-6);
test_float("-123.456789e-6", -123.456789e-6);
Examples:
Test that quoted / unquoted symbols are deserialized properly.
import mir.ion.value;
import mir.ion.conv : text2ion;
import mir.ion.stream;
void test(const(char)[] ionData, string symbol)
{
    import std.stdio;
    auto v = text2ion!(true)(ionData).IonValueStream;
    // only one value in the stream, so this is fine
    foreach (symbolTable, val; v) {
        assert(val.descriptor.type == IonTypeCode.symbol);
        auto sym = val.get!(IonSymbolID).get;
        assert(symbol == symbolTable[sym]);
    }
}

test("$0", "$0");
test("$ion", "$ion");
test("$ion_1_0", "$ion_1_0");
test("name", "name");
test("version", "version");
test("imports", "imports");
test("symbols", "symbols");
test("max_id", "max_id");
test("$ion_shared_symbol_table", "$ion_shared_symbol_table");
test("hello", "hello");
test("world", "world");
test("'foobaz'", "foobaz");
test("'👍'", "👍");
test("' '", " ");
test("'\\U0001F44D'", "👍");
test("'\\u2248'", "\u2248");
test("'true'", "true");
test("'false'", "false");
test("'nan'", "nan");
test("'null'", "null");
Examples:
Test that all variations of the "null" value are deserialized properly.
import mir.ion.value;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, IonTypeCode nullType)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(IonNull);
    assert(v.code == nullType);
}

test("null", IonTypeCode.null_);
test("null.bool", IonTypeCode.bool_);
test("null.int", IonTypeCode.uInt);
test("null.float", IonTypeCode.float_);
test("null.decimal", IonTypeCode.decimal);
test("null.timestamp", IonTypeCode.timestamp);
test("null.symbol", IonTypeCode.symbol);
test("null.string", IonTypeCode.string);
test("null.blob", IonTypeCode.blob);
test("null.clob", IonTypeCode.clob);
test("null.list", IonTypeCode.list);
test("null.struct", IonTypeCode.struct_);
test("null.sexp", IonTypeCode.sexp);
Examples:
Test that blobs are getting de-serialized correctly.
import mir.ion.value;
import mir.ion.conv : text2ion;
import mir.lob;
void test(const(char)[] ionData, ubyte[] blobData)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(Blob);
    assert(v.data == blobData);
}

test("{{ SGVsbG8sIHdvcmxkIQ== }}", cast(ubyte[])"Hello, world!");
test("{{ R29vZCBhZnRlcm5vb24hIPCfkY0= }}", cast(ubyte[])"Good afternoon! 👍");
Examples:
Test that long/short clobs are getting de-serialized correctly.
import mir.ion.value;
import mir.ion.conv : text2ion;
import mir.lob;
void test(const(char)[] ionData, const(char)[] blobData)
{
    auto v = text2ion!false(ionData).IonValue.describe.get!(Clob);
    assert(v.data == blobData);
}

test(`{{ "This is a short clob."  }}`, "This is a short clob.");
test(`
{{ 
    '''This is a long clob,'''
    ''' which spans over multiple lines,'''
    ''' and can have a theoretically infinite length.'''
}}`, "This is a long clob, which spans over multiple lines, and can have a theoretically infinite length.");
test(`{{ 
        '''Long clobs can also have their data contained in one value,
but spread out across multiple lines.'''
      }}`, "Long clobs can also have their data contained in one value,\n but spread out across multiple lines.");
test(`{{ '''Or, you can have multiple values on the same line,''' ''' like this!'''}}`, 
    "Or, you can have multiple values on the same line, like this!");
Examples:
Test that structs are getting de-serialized properly
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ion.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = text2ion!(true)(ionData).IonValueStream.serializeText;
    assert(v == expected);
}

test(`{"test":"world", test: false, 'test': usd::123.456, '''test''': "asdf"}`,
     `{test:"world",test:false,test:usd::123.456,test:"asdf"}`);

test(`{'''foo'''
'''bar''': "foobar"}`,
     `{foobar:"foobar"}`);

test(`{a: 1, b: 2}`, `{a:1,b:2}`);

test(`{}`, `{}`);
Examples:
Test that sexps are getting de-serialized properly.
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ion.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = text2ion!(true)(ionData).IonValueStream.serializeText;
    assert(v == expected);
}

test(`(this is a sexp list)`, "(this is a sexp list)");
test(`('+' '++' '+-+' '-++' '-' '--' '---' -3 - 3 '--' 3 '--'3 )`, 
    "('+' '++' '+-+' '-++' '-' '--' '---' -3 '-' 3 '--' 3 '--' 3)");
test(`(a_plus_plus_plus_operator::+++ a_3::3)`, `(a_plus_plus_plus_operator::'+++' a_3::3)`);
test(`(& (% -[42, 3]+(2)-))`, `('&' ('%' '-' [42,3] '+' (2) '-'))`);
Examples:
Test that arrays are getting de-serialized properly.
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ion.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = text2ion!(true)(ionData).IonValueStream.serializeText;
    assert(v == expected);
}

test(`[hello, world]`, `[hello,world]`);
test(`[this::is::an::annotated::symbol, this::is::annotated::123.456]`,
     `[this::is::an::annotated::symbol,this::is::annotated::123.456]`);
test(`[date::of::birth::0001-01-01T00:00:00.0-00:00, date::of::birth::1970-01-01T]`,
     `[date::of::birth::0001-01-01T00:00:00.0Z,date::of::birth::1970-01-01]`);
test(`['hello', "hello", '''hello''', '''hello ''''''world''']`,
     `[hello,"hello","hello","hello world"]`);
test(`[0x123_456, 0xF00D_BAD]`, `[1193046,251714477]`);
Examples:
Test that annotations work with symbols
import mir.ion.stream;
import mir.ion.ser.text;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = text2ion!(true)(ionData).IonValueStream.serializeText;
    assert(v == expected);
}

test(`'test'::'hello'::'world'`, "test::hello::world");
test(`foo::bar`, "foo::bar");
test(`foo::'bar'`, "foo::bar");
test(`'foo'::bar`, "foo::bar");
test(`'foo bar'::cash`, "'foo bar'::cash");
test(`'foo\U0001F44D'::'baz\U0001F44D'`, "'foo\U0001F44D'::'baz\U0001F44D'");
test(`'\u2248'::'\u2248'`, "'\u2248'::'\u2248'");
test(`'\u2248'::foo`, "'\u2248'::foo");
Examples:
Test that annotations work with floats
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ion.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = text2ion!(true)(ionData).IonValueStream.serializeText;
    assert(v == expected);
}

test(`usd::10.50e0`, "usd::10.5");
test(`'Value is good \U0001F44D'::12.34e0`, "'Value is good \U0001F44D'::12.34");
test(`'null'::150.00e0`, "'null'::150.0");
Examples:
Test that annotations work with decimals
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ion.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = text2ion!(true)(ionData).IonValueStream.serializeText;
    assert(v == expected);
}

test(`Types::Speed::MetersPerSecondSquared::9.81`, "Types::Speed::MetersPerSecondSquared::9.81");
test(`Rate::USD::GBP::12.345`, "Rate::USD::GBP::12.345");
test(`usd::10.50d0`, "usd::10.50");
test(`'Value is good \U0001F44D'::12.34d0`, "'Value is good \U0001F44D'::12.34");
test(`'null'::150.00d0`, "'null'::150.00");
test(`'Cool'::27182818284590450000000000d-25`, "Cool::2.7182818284590450000000000");
test(`mass::2.718281828459045d0`, "mass::2.718281828459045");
test(`weight::0.000000027182818284590450000000000d+8`, "weight::2.7182818284590450000000000");
test(`coeff::-0.000000027182818284590450000000000d+8`, "coeff::-2.7182818284590450000000000");
Examples:
Test that annotations work with strings
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ion.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = text2ion!(true)(ionData).IonValueStream.serializeText;
    assert(v == expected);
}

test(`Password::"Super Secure Password"`, `Password::"Super Secure Password"`);
test(`Magic::String::"Hello, world!"`, `Magic::String::"Hello, world!"`);
test(`SSH::PublicKey::'''ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNrMk7QmmmNIusf10CwHQHs6Z9HJIiuknwoqtQLzEPxdMnNHKJexNnfF5QQ2v84BBhVjxvPgSqhdcVMEFy8PrGu44MqhK/cV6BGx430v2FnArWDO+9LUSd+3iwMJVZUQgZGtjSLAkZO+NOSPWZ+W0SODGgUfbNVu35GjVoA2+e1lOINUe22oZPnaD+gpJGUOx7j5JqpCblBZntvZyOjTPl3pc52rIGfxi1TYJnDXjqX76OinZceBzp5Oh0oUTrPbu55ig+b8bd4HtzLWxcqXBCnsw0OAKsAiXfLlBcrgZUsoAP9unrcqsqoJ2qEEumdsPqcpJakpO7/n0lMP6lRdSZ'''`,
     `SSH::PublicKey::"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNrMk7QmmmNIusf10CwHQHs6Z9HJIiuknwoqtQLzEPxdMnNHKJexNnfF5QQ2v84BBhVjxvPgSqhdcVMEFy8PrGu44MqhK/cV6BGx430v2FnArWDO+9LUSd+3iwMJVZUQgZGtjSLAkZO+NOSPWZ+W0SODGgUfbNVu35GjVoA2+e1lOINUe22oZPnaD+gpJGUOx7j5JqpCblBZntvZyOjTPl3pc52rIGfxi1TYJnDXjqX76OinZceBzp5Oh0oUTrPbu55ig+b8bd4HtzLWxcqXBCnsw0OAKsAiXfLlBcrgZUsoAP9unrcqsqoJ2qEEumdsPqcpJakpO7/n0lMP6lRdSZ"`);