1 /++ 2 Conversion utilities. 3 +/ 4 module mir.ion.conv; 5 6 public import mir.ion.internal.stage3: mir_json2ion; 7 import mir.ion.stream: IonValueStream; 8 9 private enum dip1000 = __traits(compiles, ()@nogc { throw new Exception(""); }); 10 11 /++ 12 Serialize value to binary ion data and deserialize it back to requested type. 13 Uses GC allocated string tables. 14 +/ 15 template serde(T) 16 if (!is(immutable T == immutable IonValueStream)) 17 { 18 import mir.serde: SerdeTarget; 19 20 /// 21 T serde(V)(auto scope ref const V value, int serdeTarget = SerdeTarget.ion) 22 { 23 T target; 24 serde(target, value, serdeTarget); 25 return target; 26 } 27 28 /// 29 void serde(V)(scope ref T target, auto ref scope const V value, int serdeTarget = SerdeTarget.ion) 30 if (!is(immutable V == immutable IonValueStream)) 31 { 32 import mir.ion.exception; 33 import mir.deser.ion: deserializeIon; 34 import mir.ion.internal.data_holder: ionPrefix; 35 import mir.ser: serializeValue; 36 import mir.ser.ion: ionSerializer; 37 import mir.ion.symbol_table: IonSymbolTable, removeSystemSymbols, IonSystemSymbolTable_v1; 38 import mir.ion.value: IonValue, IonDescribedValue, IonList; 39 import mir.serde: serdeGetSerializationKeysRecurse; 40 import mir.utility: _expect; 41 42 enum nMax = 4096; 43 enum keys = serdeGetSerializationKeysRecurse!V.removeSystemSymbols; 44 45 46 import mir.appender : scopedBuffer; 47 auto symbolTableBuffer = scopedBuffer!(const(char)[]); 48 49 auto table = () @trusted { IonSymbolTable!false ret = void; ret.initializeNull; return ret; }(); 50 auto serializer = ionSerializer!(nMax * 8, keys, false); 51 serializer.initialize(table); 52 serializeValue(serializer, value); 53 serializer.finalize; 54 55 scope const(const(char)[])[] symbolTable; 56 57 // use runtime table 58 if (table.initialized) 59 { 60 symbolTableBuffer.put(IonSystemSymbolTable_v1); 61 foreach (IonErrorCode error, scope IonDescribedValue symbolValue; IonList(table.unfinilizedKeysData)) 62 { 63 assert(!error); 64 (()@trusted => symbolTableBuffer.put(symbolValue.trustedGet!(const(char)[])))(); 65 } 66 symbolTable = symbolTableBuffer.data; 67 } 68 else 69 { 70 static immutable compileTimeTable = IonSystemSymbolTable_v1 ~ keys; 71 symbolTable = compileTimeTable; 72 } 73 auto ionValue = ()@trusted {return serializer.data.IonValue.describe();}(); 74 return deserializeIon!T(target, symbolTable, ionValue); 75 } 76 77 /// ditto 78 void serde()(scope ref T target, scope IonValueStream stream, int serdeTarget = SerdeTarget.ion) 79 if (!is(immutable V == immutable IonValueStream)) 80 { 81 import mir.deser.ion: deserializeIon; 82 return deserializeIon!T(target, stream.data); 83 } 84 } 85 86 /// ditto 87 template serde(T) 88 if (is(T == IonValueStream)) 89 { 90 /// 91 import mir.serde: SerdeTarget; 92 T serde(V)(auto ref scope const V value, int serdeTarget = SerdeTarget.ion) 93 if (!is(immutable V == immutable IonValueStream)) 94 { 95 import mir.ser.ion: serializeIon; 96 return serializeIon(value, serdeTarget).IonValueStream; 97 } 98 } 99 100 101 /// 102 version(mir_ion_test) 103 @safe 104 unittest { 105 import mir.ion.stream: IonValueStream; 106 import mir.algebraic_alias.json: JsonAlgebraic; 107 static struct S 108 { 109 double a; 110 string s; 111 } 112 auto s = S(12.34, "str"); 113 assert(s.serde!JsonAlgebraic.serde!S == s); 114 assert(s.serde!IonValueStream.serde!S == s); 115 } 116 117 @safe pure 118 version(mir_ion_test) 119 unittest { 120 static struct S 121 { 122 double a; 123 string s; 124 } 125 auto s = S(12.34, "str"); 126 assert(s.serde!S == s); 127 assert(s.serde!IonValueStream.serde!S == s); 128 } 129 130 /++ 131 Converts JSON Value Stream to binary Ion data. 132 +/ 133 immutable(ubyte)[] json2ion(scope const(char)[] text) 134 @trusted pure 135 { 136 pragma(inline, false); 137 import mir.ion.exception: ionErrorMsg, IonParserMirException; 138 139 immutable(ubyte)[] ret; 140 mir_json2ion(text, (error, data) 141 { 142 if (error.code) 143 throw new IonParserMirException(error.code.ionErrorMsg, error.location); 144 ret = data.idup; 145 }); 146 return ret; 147 } 148 149 /// 150 @safe pure 151 version(mir_ion_test) unittest 152 { 153 import mir.test; 154 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x62, 0x81, 0x61, 0xd6, 0x8b, 0x21, 0x01, 0x8a, 0x21, 0x02]; 155 `{"a":1,"b":2}`.json2ion.should == data; 156 } 157 158 /++ 159 Convert an JSON value to a Ion Value Stream. 160 161 This function is the @nogc version of json2ion. 162 Params: 163 text = The JSON to convert 164 appender = A buffer that will receive the Ion binary data 165 +/ 166 void json2ion(Appender)(scope const(char)[] text, scope ref Appender appender) 167 @trusted pure @nogc 168 { 169 import mir.ion.exception: ionErrorMsg, ionException, IonMirException; 170 import mir.ion.internal.data_holder: ionPrefix; 171 172 mir_json2ion(text, (error, data) 173 { 174 if (error.code) 175 { 176 enum nogc = __traits(compiles, (const(ubyte)[] data, scope ref Appender appender) @nogc { appender.put(data); }); 177 static if (!nogc || dip1000) 178 { 179 throw new IonMirException(error.code.ionErrorMsg, ". location = ", error.location, ", last input key = ", error.key); 180 } 181 else 182 { 183 throw error.code.ionException; 184 } 185 } 186 appender.put(data); 187 }); 188 } 189 190 /// 191 @safe pure 192 version(mir_ion_test) unittest 193 { 194 import mir.test; 195 import mir.appender : scopedBuffer; 196 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x62, 0x81, 0x61, 0xd6, 0x8b, 0x21, 0x01, 0x8a, 0x21, 0x02]; 197 auto buf = scopedBuffer!ubyte; 198 json2ion(` { "a" : 1, "b" : 2 } `, buf); 199 buf.data.should == data; 200 } 201 202 /++ 203 Converts JSON Value Stream to binary Ion data wrapped to $(SUBREF stream, IonValueStream). 204 +/ 205 IonValueStream json2ionStream(scope const(char)[] text) 206 @trusted pure 207 { 208 return text.json2ion.IonValueStream; 209 } 210 211 /// 212 @safe pure 213 version(mir_ion_test) unittest 214 { 215 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x62, 0x81, 0x61, 0xd6, 0x8b, 0x21, 0x01, 0x8a, 0x21, 0x02]; 216 assert(`{"a":1,"b":2}`.json2ionStream.data == data); 217 } 218 219 /++ 220 Converts Ion Value Stream data to JSON text. 221 222 The function performs `data.IonValueStream.serializeJson`. 223 +/ 224 string ion2json(scope const(ubyte)[] data) 225 @safe pure 226 { 227 pragma(inline, false); 228 import mir.ser.json: serializeJson; 229 return data.IonValueStream.serializeJson; 230 } 231 232 /// 233 @safe pure 234 version(mir_ion_test) unittest 235 { 236 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 237 assert(data.ion2json == `{"a":1,"b":2}`); 238 // static assert(data.ion2json == `{"a":1,"b":2}`); 239 } 240 241 version(mir_ion_test) unittest 242 { 243 assert("".json2ion.ion2text == ""); 244 } 245 246 /++ 247 Converts Ion Value Stream data to JSON text 248 249 The function performs `data.IonValueStream.serializeJsonPretty`. 250 +/ 251 string ion2jsonPretty(scope const(ubyte)[] data) 252 @safe pure 253 { 254 pragma(inline, false); 255 import mir.ser.json: serializeJsonPretty; 256 return data.IonValueStream.serializeJsonPretty; 257 } 258 259 /// 260 @safe pure 261 version(mir_ion_test) unittest 262 { 263 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 264 assert(data.ion2jsonPretty == "{\n\t\"a\": 1,\n\t\"b\": 2\n}"); 265 // static assert(data.ion2jsonPretty == "{\n\t\"a\": 1,\n\t\"b\": 2\n}"); 266 } 267 268 /++ 269 Convert an Ion Text value to a Ion data. 270 Params: 271 text = The text to convert 272 Returns: 273 An array containing the Ion Text value as an Ion data. 274 +/ 275 immutable(ubyte)[] text2ion(scope const(char)[] text) 276 @trusted pure 277 { 278 import mir.ion.internal.data_holder: ionPrefix; 279 import mir.ion.symbol_table: IonSymbolTable; 280 import mir.ion.internal.data_holder: ionPrefix; 281 import mir.ser.ion : ionSerializer; 282 import mir.serde : SerdeTarget; 283 import mir.deser.text : IonTextDeserializer; 284 enum nMax = 4096; 285 286 IonSymbolTable!true table; 287 auto serializer = ionSerializer!(nMax * 8, null, true); 288 serializer.initialize(table); 289 290 auto deser = IonTextDeserializer!(typeof(serializer))(&serializer); 291 deser(text); 292 serializer.finalize; 293 294 if (table.initialized) 295 { 296 table.finalize; 297 return cast(immutable) (ionPrefix ~ table.data ~ serializer.data); 298 } 299 else 300 { 301 return cast(immutable) (ionPrefix ~ serializer.data); 302 } 303 } 304 /// 305 @safe pure 306 version(mir_ion_test) unittest 307 { 308 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 309 assert(`{"a":1,"b":2}`.text2ion == data); 310 static assert(`{"a":1,"b":2}`.text2ion == data); 311 enum s = `{a:2.232323e2, b:2.1,}`.text2ion; 312 } 313 314 /++ 315 Converts Ion Text Value Stream to binary Ion data wrapped to $(SUBREF stream, IonValueStream). 316 +/ 317 IonValueStream text2ionStream(scope const(char)[] text) 318 @trusted pure 319 { 320 return text.text2ion.IonValueStream; 321 } 322 323 /// 324 @safe pure 325 version(mir_ion_test) unittest 326 { 327 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 328 assert(`{a:1,b:2}`.text2ionStream.data == data); 329 } 330 331 /++ 332 Convert an Ion Text value to a Ion Value Stream. 333 334 This function is the @nogc version of text2ion. 335 Params: 336 text = The text to convert 337 appender = A buffer that will receive the Ion binary data 338 +/ 339 void text2ion(Appender)(scope const(char)[] text, scope ref Appender appender) 340 @trusted 341 { 342 import mir.ion.internal.data_holder: ionPrefix; 343 import mir.ion.symbol_table: IonSymbolTable; 344 import mir.ion.internal.data_holder: ionPrefix; 345 import mir.ser.ion : ionSerializer; 346 import mir.serde : SerdeTarget; 347 import mir.deser.text : IonTextDeserializer; 348 enum nMax = 4096; 349 IonSymbolTable!false table = void; 350 table.initialize; 351 352 auto serializer = ionSerializer!(nMax * 8, null, false); 353 serializer.initialize(table); 354 355 auto deser = IonTextDeserializer!(typeof(serializer))(&serializer); 356 357 deser(text); 358 serializer.finalize; 359 360 appender.put(ionPrefix); 361 if (table.initialized) 362 { 363 table.finalize; 364 appender.put(table.data); 365 } 366 appender.put(serializer.data); 367 } 368 /// 369 @safe pure @nogc 370 version(mir_ion_test) unittest 371 { 372 import mir.appender : scopedBuffer; 373 static immutable data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 374 auto buf = scopedBuffer!ubyte; 375 text2ion("{\n\ta: 1,\n\tb: 2\n}", buf); 376 assert(buf.data == data); 377 } 378 379 /++ 380 Converts Ion Value Stream data to text. 381 382 The function performs `data.IonValueStream.serializeText`. 383 +/ 384 string ion2text(scope const(ubyte)[] data) 385 @safe pure 386 { 387 pragma(inline, false); 388 import mir.ser.text: serializeText; 389 return data.IonValueStream.serializeText; 390 } 391 392 /// 393 @safe pure 394 version(mir_ion_test) unittest 395 { 396 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 397 assert(data.ion2text == `{a:1,b:2}`); 398 // static assert(data.ion2text == `{a:1,b:2}`); 399 } 400 401 /// 402 @safe pure 403 version(mir_ion_test) unittest 404 { 405 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xea, 0x81, 0x83, 0xde, 0x86, 0x87, 0xb4, 0x83, 0x55, 0x53, 0x44, 0xe6, 0x81, 0x8a, 0x53, 0xc1, 0x04, 0xd2]; 406 assert(data.ion2text == `USD::123.4`); 407 // static assert(data.ion2text == `USD::123.4`); 408 } 409 410 // 411 412 /++ 413 Converts Ion Value Stream data to text 414 415 The function performs `data.IonValueStream.serializeTextPretty`. 416 +/ 417 string ion2textPretty(scope const(ubyte)[] data) 418 @safe pure 419 { 420 pragma(inline, false); 421 import mir.ser.text: serializeTextPretty; 422 return data.IonValueStream.serializeTextPretty; 423 } 424 425 /// 426 @safe pure 427 version(mir_ion_test) unittest 428 { 429 static immutable ubyte[] data = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 430 assert(data.ion2textPretty == "{\n\ta: 1,\n\tb: 2\n}"); 431 // static assert(data.ion2textPretty == "{\n\ta: 1,\n\tb: 2\n}"); 432 } 433 434 void msgpack2ion(Appender)(scope const(ubyte)[] data, scope ref Appender appender) 435 @trusted 436 { 437 import mir.ion.internal.data_holder: ionPrefix; 438 import mir.ion.symbol_table: IonSymbolTable; 439 import mir.ion.internal.data_holder: ionPrefix; 440 import mir.ser.ion : ionSerializer; 441 import mir.serde : SerdeTarget; 442 import mir.deser.msgpack : MsgpackValueStream; 443 enum nMax = 4096; 444 445 IonSymbolTable!false table = void; 446 table.initialize; 447 auto serializer = ionSerializer!(nMax * 8, null, false); 448 serializer.initialize(table); 449 450 data.MsgpackValueStream.serialize(serializer); 451 serializer.finalize; 452 453 appender.put(ionPrefix); 454 if (table.initialized) 455 { 456 table.finalize; 457 appender.put(table.data); 458 } 459 appender.put(serializer.data); 460 } 461 462 /++ 463 Converts MessagePack binary data to Ion binary data. 464 +/ 465 @safe pure 466 immutable(ubyte)[] msgpack2ion()(scope const(ubyte)[] data) 467 { 468 import mir.appender : scopedBuffer; 469 auto buf = scopedBuffer!ubyte; 470 data.msgpack2ion(buf); 471 return buf.data.idup; 472 } 473 474 @safe pure @nogc 475 version(mir_ion_test) unittest 476 { 477 import mir.appender : scopedBuffer; 478 import mir.deser.ion : deserializeIon; 479 static struct S 480 { 481 bool compact; 482 int schema; 483 } 484 485 auto buf = scopedBuffer!ubyte(); 486 static immutable ubyte[] data = [0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x04]; 487 data.msgpack2ion(buf); 488 assert(buf.data.deserializeIon!S == S(true, 4)); 489 } 490 491 @safe pure 492 version(mir_ion_test) unittest 493 { 494 static immutable testStrings = [ 495 "2018-01-02T03:04:05Z", 496 "2018-01-02T03:04:05.678901234Z", 497 "2038-01-19T03:14:07.999999999Z", 498 "2038-01-19T03:14:08Z", 499 "2038-01-19T03:14:08.000000001Z", 500 "2106-02-07T06:28:15Z", 501 "2106-02-07T06:28:15.999999999Z", 502 "2106-02-07T06:28:16.000000000Z", 503 "2514-05-30T01:53:03.999999999Z", 504 "2514-05-30T01:53:04.000000000Z", 505 "1969-12-31T23:59:59.000000000Z", 506 "1969-12-31T23:59:59.999999999Z", 507 "1970-01-01T00:00:00Z", 508 "1970-01-01T00:00:00.000000001Z", 509 "1970-01-01T00:00:01Z", 510 "1899-12-31T23:59:59.999999999Z", 511 "1900-01-01T00:00:00.000000000Z", 512 "9999-12-31T23:59:59.999999999Z", 513 ]; 514 515 static immutable ubyte[][] testData = [ 516 [0xd6, 0xff, 0x5a, 0x4a, 0xf6, 0xa5], 517 [0xd7, 0xff, 0xa1, 0xdc, 0xd7, 0xc8, 0x5a, 0x4a, 0xf6, 0xa5], 518 [0xd7, 0xff, 0xee, 0x6b, 0x27, 0xfc, 0x7f, 0xff, 0xff, 0xff], 519 [0xd6, 0xff, 0x80, 0x00, 0x00, 0x00], 520 [0xd7, 0xff, 0x00, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x00], 521 [0xd6, 0xff, 0xff, 0xff, 0xff, 0xff], 522 [0xd7, 0xff, 0xee, 0x6b, 0x27, 0xfc, 0xff, 0xff, 0xff, 0xff], 523 [0xd7, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], 524 [0xd7, 0xff, 0xee, 0x6b, 0x27, 0xff, 0xff, 0xff, 0xff, 0xff], 525 [0xc7, 0x0c, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00], 526 [0xc7, 0x0c, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 527 [0xc7, 0x0c, 0xff, 0x3b, 0x9a, 0xc9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 528 [0xd6, 0xff, 0x00, 0x00, 0x00, 0x00], 529 [0xd7, 0xff, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00], 530 [0xd6, 0xff, 0x00, 0x00, 0x00, 0x01], 531 [0xc7, 0x0c, 0xff, 0x3b, 0x9a, 0xc9, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0x55, 0x81, 0x7f], 532 [0xc7, 0x0c, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x7c, 0x55, 0x81, 0x80], 533 [0xc7, 0x0c, 0xff, 0x3b, 0x9a, 0xc9, 0xff, 0x00, 0x00, 0x00, 0x3a, 0xff, 0xf4, 0x41, 0x7f], 534 ]; 535 536 foreach (i, ts; testStrings) 537 { 538 import mir.ser.ion : serializeIon; 539 import mir.timestamp : Timestamp; 540 auto mp = testData[i]; 541 auto ion = mp.msgpack2ion; 542 import mir.test; 543 ion.ion2text.should == ts; 544 ts.Timestamp.serializeIon.ion2msgpack.should == mp; 545 } 546 } 547 548 /++ 549 Converts Ion binary data to MessagePack binary data. 550 +/ 551 @safe pure 552 immutable(ubyte)[] ion2msgpack()(scope const(ubyte)[] data) 553 { 554 import mir.ser.msgpack: serializeMsgpack; 555 return data.IonValueStream.serializeMsgpack; 556 } 557 558 @safe pure 559 version(mir_ion_test) unittest 560 { 561 import mir.test; 562 foreach(text; [ 563 `null`, 564 `true`, 565 `1`, 566 `-2`, 567 `3.0`, 568 `2001-01-02T03:04:05Z`, 569 `[]`, 570 `[1,-2,3.0]`, 571 `[null,true,[1,-2,3.0],2001-01-02T03:04:05Z]`, 572 `{}`, 573 `{d:2001-01-02T03:04:05Z}`, 574 ]) 575 text.text2ion.ion2msgpack.msgpack2ion.ion2text.should == text; 576 }