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 @nogc struct deserialization
import mir.ion.value;
import mir.bignum.decimal;
import mir.small_string;
import mir.small_array;
import mir.conv : to;
static struct Book
{
    SmallString!64 title;
    bool wouldRecommend;
    SmallString!64 description;
    uint numberOfNovellas;
    Decimal!1 price;
    double weight;
    SmallArray!(SmallString!(16), 10) 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.to!double == 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.stream;
import mir.ion.conv : text2ion;
import mir.ion.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    const(char)[] output = ionData.text2ion.IonValueStream.serializeText;
    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.stream;
import mir.ion.conv : text2ion;
import mir.ion.value : IonTimestamp;
import std.datetime.date : TimeOfDay;
import mir.timestamp : Timestamp;
void test(const(char)[] ionData, Timestamp expected)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        Timestamp t = ionValue.get!(IonTimestamp).get;
        assert(expected == t);
    }
}

void testFail(const(char)[] ionData, Timestamp expected)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        Timestamp t = ionValue.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));

// Time of day tests
test("03:04", Timestamp(0, 0, 0, 3, 4));
test("03:04Z", Timestamp(0, 0, 0, 3, 4));
test("03:04+00:00", Timestamp(0, 0, 0, 3, 4));
test("03:05+00:01", Timestamp(0, 0, 0, 3, 4).withOffset(1));
test("05:05+02:01", Timestamp(0, 0, 0, 3, 4).withOffset(2*60+1));
test("03:04:05", Timestamp(0, 0, 0, 3, 4, 5));
test("03:04:05Z", Timestamp(0, 0, 0, 3, 4, 5));
test("03:04:05+00:00", Timestamp(0, 0, 0, 3, 4, 5));
test("03:05:05+00:01", Timestamp(0, 0, 0, 3, 4, 5).withOffset(1));
test("05:05:05+02:01", Timestamp(0, 0, 0, 3, 4, 5).withOffset(2*60+1));
test("03:04:05.666", Timestamp(0, 0, 0, 3, 4, 5, -3, 666));
test("03:04:05.666Z", Timestamp(0, 0, 0, 3, 4, 5, -3, 666));
test("03:04:05.666666Z", Timestamp(0, 0, 0, 3, 4, 5, -6, 666_666));
test("03:54:05.666+00:50", Timestamp(0, 0, 0, 3, 4, 5, -3, 666).withOffset(50));
test("03:54:05.666666+00:50", Timestamp(0, 0, 0, 3, 4, 5, -6, 666_666).withOffset(50));

// Mir doesn't like 03:04 only (as technically it's less precise then TimeOfDay)... ugh
test("03:04:05", Timestamp(TimeOfDay(3, 4, 5)));
test("03:04:05Z", Timestamp(TimeOfDay(3, 4, 5)));
test("03:04:05+00:00", Timestamp(TimeOfDay(3, 4, 5)));
test("03:05:05+00:01", Timestamp(TimeOfDay(3, 4, 5)).withOffset(1));
test("05:05:05+02:01", Timestamp(TimeOfDay(3, 4, 5)).withOffset(2*60+1));

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));

// Fake timestamps for Duration encoding
import core.time : weeks, days, hours, minutes, seconds, hnsecs;
test("0005-02-88T07:40:04.9876543", Timestamp(5.weeks + 2.days + 7.hours + 40.minutes + 4.seconds + 9876543.hnsecs));
test("0005-02-99T07:40:04.9876543", Timestamp(-5.weeks - 2.days - 7.hours - 40.minutes - 4.seconds - 9876543.hnsecs));
Examples:
Test that binary literals are de-serialized properly.
import mir.ion.value : IonUInt;
import mir.ion.stream;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, uint val)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonUInt);
        assert(v.get!uint == val);
    }
}

test("0b00001", 0b1);
test("0b10101", 0b10101);
test("0b11111", 0b11111);
test("0b111111111111111111111", 0b1111_1111_1111_1111_1111_1);
test("0b1_1111_1111_1111_1111_1111", 0b1_1111_1111_1111_1111_1111);
Examples:
Test that signed / unsigned integers are de-serialized properly.
import mir.ion.value : IonUInt, IonNInt;
import mir.ion.stream;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, ulong val)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonUInt);
        assert(v.get!ulong == val);
    }
}

void testNeg(const(char)[] ionData, ulong val)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.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 : IonFloat;
import mir.ion.conv : text2ion;
import mir.ion.stream;
void test(const(char)[] ionData, float expected)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.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;
import mir.ion.stream;

alias isNaN = x => x != x;
void test(const(char)[] ionData)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.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.stream;
import mir.ion.conv : text2ion;
void test_uint(const(char)[] ionData, ulong expected)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonUInt);
        assert(v.get!ulong == expected);
    }
}

void test_nint(const(char)[] ionData, long expected)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonNInt);
        assert(v.get!long == expected);
    }
}

void test_dec(const(char)[] ionData, double expected)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonDecimal);
        assert(v.get!double == expected);
    }
}

void test_float(const(char)[] ionData, float expected)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.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)
{
    foreach (symbolTable, val; ionData.text2ion.IonValueStream) {
        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.stream;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, IonTypeCode nullType)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.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.stream;
import mir.ion.conv : text2ion;
import mir.lob;
void test(const(char)[] ionData, ubyte[] blobData)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.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.stream;
import mir.ion.conv : text2ion;
import mir.lob;
void test(const(char)[] ionData, const(char)[] blobData)
{
    foreach(symbolTable, ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.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 = ionData.text2ion.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 = ionData.text2ion.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 = ionData.text2ion.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 = ionData.text2ion.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 = ionData.text2ion.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 = ionData.text2ion.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 = ionData.text2ion.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"`);