1 /++
2 +/
3 module mir.deser.low_level;
4 
5 import mir.appender: scopedBuffer, ScopedBuffer;
6 import mir.bignum.decimal: Decimal;
7 import mir.bignum.integer: BigInt;
8 import mir.ion.exception;
9 import mir.ion.internal.basic_types;
10 import mir.ion.type_code;
11 import mir.ion.value;
12 import mir.rc.array: RCArray;
13 import mir.small_array;
14 import mir.small_string;
15 import mir.timestamp;
16 import mir.utility: _expect;
17 import mir.serde: serdeGetFinalProxy;
18 import mir.lob : Clob, Blob;
19 
20 import std.traits:
21     isArray,
22     isAggregateType,
23     ForeachType,
24     hasUDA,
25     isFloatingPoint,
26     isIntegral,
27     isSigned,
28     isSomeChar,
29     isUnsigned,
30     Unqual;
31 
32 template isFirstOrderSerdeType(T)
33 {
34     import mir.serde: serdeGetFinalProxy, serdeLikeStruct, serdeLikeList;
35 
36     static if (isAggregateType!T)
37     {
38         static if (is(T == Clob) || is(T == Blob))
39             enum isFirstOrderSerdeType = true;
40         else
41         static if (isBigInt!T)
42             enum isFirstOrderSerdeType = true;
43         else
44         static if (isDecimal!T)
45             enum isFirstOrderSerdeType = true;
46         else
47         static if (isTimestamp!T || is(typeof(Timestamp(T.init))) && __traits(getAliasThis, T).length == 0)
48             enum isFirstOrderSerdeType = true;
49         else
50         static if (is(T : SmallString!maxLength, size_t maxLength))
51             enum isFirstOrderSerdeType = false;
52         else
53         static if (is(T : SmallArray!(E, maxLength), E, size_t maxLength))
54             enum isFirstOrderSerdeType = isFirstOrderSerdeType!E;
55         else
56         static if (is(T : RCArray!E, E))
57             enum isFirstOrderSerdeType = isFirstOrderSerdeType!E;
58         else
59         static if (hasUDA!(T, serdeLikeStruct) || hasUDA!(T, serdeLikeList))
60             enum isFirstOrderSerdeType = false;
61         else
62         static if (is(T == serdeGetFinalProxy!T))
63             enum isFirstOrderSerdeType = false;
64         else
65             enum isFirstOrderSerdeType = isFirstOrderSerdeType!(serdeGetFinalProxy!T);
66     }
67     else
68     static if (isArray!T)
69         enum isFirstOrderSerdeType = .isFirstOrderSerdeType!(Unqual!(ForeachType!T));
70     else
71     static if (is(T == V[K], K, V))
72         enum isFirstOrderSerdeType = false;
73     else
74     static if (is(T == enum))
75         enum isFirstOrderSerdeType = false;
76     else
77     static if (isSomeChar!T)
78         enum isFirstOrderSerdeType = false;
79     else
80     static if (is(T == serdeGetFinalProxy!T))
81         enum isFirstOrderSerdeType = true;
82     else
83         enum isFirstOrderSerdeType = isFirstOrderSerdeType!(serdeGetFinalProxy!T, false);
84 }
85 
86 version(mir_ion_test)
87 unittest
88 {
89     import std.datetime.date;
90     import std.datetime.systime;
91     static assert(isFirstOrderSerdeType!Date);
92     static assert(isFirstOrderSerdeType!DateTime);
93     static assert(isFirstOrderSerdeType!SysTime);
94 }
95 
96 version(mir_ion_test)
97 unittest
98 {
99     import mir.date;
100     static assert(isFirstOrderSerdeType!Date);
101 }
102 
103 /++
104 Deserialize `null` value
105 +/
106 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
107     pure @safe nothrow @nogc
108     if (is(T == typeof(null)))
109 {
110     version (LDC) pragma(inline, true);
111     return data == null ? IonErrorCode.none : IonErrorCode.expectedNullValue;
112 }
113 
114 ///
115 version(mir_ion_test) unittest
116 {
117     import mir.ion.value;
118     import mir.ion.exception;
119 
120     auto data = IonValue([0x1F]).describe; // null.bool
121     typeof(null) value;
122     assert(deserializeValueImpl!(typeof(null))(data, value) == IonErrorCode.none);
123 }
124 
125 /++
126 Deserialize boolean value
127 +/
128 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
129     pure @safe nothrow @nogc
130     if (is(T == bool))
131 {
132     IonErrorCode error;
133     value = data.get!bool(error);
134     return error;
135 }
136 
137 ///
138 pure version(mir_ion_test) unittest
139 {
140     import mir.ion.value;
141     import mir.ion.exception;
142 
143     auto data = IonValue([0x11]).describe;
144     bool value;
145     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
146     assert(value);
147 }
148 
149 /++
150 Deserialize integral value.
151 +/
152 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
153     pure @safe nothrow @nogc
154     if (isIntegral!T && !is(T == enum))
155 {
156     static if (__traits(isUnsigned, T))
157     {
158         IonErrorCode error;
159         auto ionValue = data.get!IonUInt(error);
160         if (error)
161             return error;
162         return ionValue.get!T(value);
163     }
164     else
165     {
166         IonErrorCode error;
167         auto ionValue = data.get!IonInt(error);
168         if (error)
169             return error;
170         return ionValue.get!T(value);
171     }
172 }
173 
174 ///
175 version(mir_ion_test) unittest
176 {
177     import mir.ion.value;
178     import mir.ion.exception;
179 
180     auto data = IonValue([0x21, 0x07]).describe;
181     int valueS;
182     uint valueU;
183 
184     assert(deserializeValueImpl(data, valueS) == IonErrorCode.none);
185     assert(valueS == 7);
186 
187     assert(deserializeValueImpl(data, valueU) == IonErrorCode.none);
188     assert(valueU == 7);
189 
190     data = IonValue([0x31, 0x07]).describe;
191 
192     assert(deserializeValueImpl(data, valueS) == IonErrorCode.none);
193     assert(valueS == -7);
194 }
195 
196 /++
197 Deserialize big integer value.
198 +/
199 IonErrorCode deserializeValueImpl(T : BigInt!maxSize64, size_t maxSize64)(scope IonDescribedValue data, scope ref T value)
200     pure @safe nothrow @nogc
201 {
202     IonErrorCode error;
203     auto ionValue = data.get!IonInt(error);
204     if (error)
205         return error;
206     if (!value.copyFromBigEndian(ionValue.data, ionValue.sign))
207         return IonErrorCode.integerOverflow;
208     return IonErrorCode.none;
209 }
210 
211 ///
212 version(mir_ion_test) unittest
213 {
214     import mir.ion.value;
215     import mir.ion.exception;
216     import mir.bignum.integer;
217 
218     auto data = IonValue([0x31, 0x07]).describe;
219     BigInt!128 value = void; // 256x64
220 
221     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
222     assert(value.sign);
223     assert(value.view.unsigned == 7);
224 }
225 
226 /++
227 Deserialize Blob value.
228 +/
229 IonErrorCode deserializeValueImpl()(scope IonDescribedValue data, ref Blob value)
230     pure @safe nothrow
231 {
232     IonErrorCode error;
233     auto ionValue = data.get!Blob(error);
234     if (error)
235         return error;
236     value = ionValue.data.dup.Blob;
237     return IonErrorCode.none;
238 }
239 
240 ///
241 version(mir_ion_test) unittest
242 {
243     import mir.deser.ion : deserializeIon;
244     import mir.lob : Blob;
245     import mir.ser.ion : serializeIon;
246 
247     static struct BlobWrapper { Blob blob; }
248     auto blob = BlobWrapper(Blob([0xF0, 0x00, 0xBA, 0x50]));
249     auto serdeBlob = serializeIon(blob).deserializeIon!BlobWrapper;
250     assert(serdeBlob == blob);
251 }
252 
253 /++
254 Deserialize Clob value.
255 +/
256 IonErrorCode deserializeValueImpl()(scope IonDescribedValue data, ref Clob value)
257     pure @safe nothrow
258 {
259     IonErrorCode error;
260     auto ionValue = data.get!Clob(error);
261     if (error)
262         return error;
263     value = ionValue.data.dup.Clob;
264     return IonErrorCode.none;
265 }
266 
267 ///
268 version(mir_ion_test) unittest
269 {
270     import mir.deser.ion : deserializeIon;
271     import mir.lob : Clob;
272     import mir.ser.ion : serializeIon;
273 
274     static struct ClobWrapper { Clob clob; }
275     auto clob = ClobWrapper(Clob("abcd"));
276     auto serdeClob = serializeIon(clob).deserializeIon!ClobWrapper;
277     assert(serdeClob == clob);
278 }
279 
280 /++
281 Deserialize floating point value.
282 
283 Special_deserialisation_symbol_values:
284 
285 $(TABLE
286     $(TR $(TD `nan"`))
287     $(TR $(TD `+inf"`))
288     $(TR $(TD `-inf`))
289 )
290 +/
291 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
292     pure @safe nothrow @nogc
293     if (isFloatingPoint!T)
294 {
295     if (_expect(data != null, true))
296     {
297         if (data.descriptor.type == IonTypeCode.float_)
298         {
299             return data.trustedGet!IonFloat.get!T(value);
300         }
301         else
302         if (data.descriptor.type == IonTypeCode.decimal)
303         {
304             return data.trustedGet!IonDecimal.get!T(value);
305         }
306         else
307         if (data.descriptor.type == IonTypeCode.uInt || data.descriptor.type == IonTypeCode.nInt)
308         {
309             IonErrorCode error;
310             auto ionValue = data.get!IonInt(error);
311             if (error)
312                 return error;
313             import mir.bignum.integer;
314             BigInt!128 integer = void;
315             if(!integer.copyFromBigEndian(ionValue.data, ionValue.sign))
316                 return IonErrorCode.overflowInIntegerValue;
317             value = cast(T) integer;
318             return IonErrorCode.none;
319         }
320         else
321         {
322             IonErrorCode error;
323             auto ionValue = data.get!(const(char)[])(error);
324             if (error)
325                 return error;
326 
327             import mir.bignum.decimal;
328             Decimal!128 decimal = void;
329             DecimalExponentKey exponentKey;
330 
331             enum bool allowSpecialValues = true;
332             enum bool allowDotOnBounds = true;
333             enum bool allowDExponent = true;
334             enum bool allowStartingPlus = true;
335             enum bool allowUnderscores = false;
336             enum bool allowLeadingZeros = true;
337             enum bool allowExponent = true;
338             enum bool checkEmpty = false;
339 
340             if (!decimal.fromStringImpl!(
341                 char,
342                 allowSpecialValues,
343                 allowDotOnBounds,
344                 allowDExponent,
345                 allowStartingPlus,
346                 allowUnderscores,
347                 allowLeadingZeros,
348                 allowExponent,
349                 checkEmpty,
350             )(ionValue, exponentKey))
351                 return IonErrorCode.expectedFloatingValue;
352 
353             value = cast(T) decimal;
354             return IonErrorCode.none;
355         }
356     }
357     return IonErrorCode.expectedFloatingValue;
358 }
359 
360 ///
361 version(mir_ion_test) unittest
362 {
363     import mir.ion.value;
364     import mir.ion.exception;
365     // from ion float
366     auto data = IonValue([0x44, 0x42, 0xAA, 0x40, 0x00]).describe;
367     double value;
368 
369     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
370     assert(value == 85.125);
371 
372     // from ion double
373     data = IonValue([0x48, 0x40, 0x55, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00]).describe;
374 
375     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
376     assert(value == 85.125);
377 
378     // from ion decimal
379     data = IonValue([0x56, 0x00, 0xcb, 0x80, 0xbc, 0x2d, 0x86]).describe;
380 
381     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
382     assert(value == -12332422e75);
383 }
384 
385 /++
386 Deserialize decimal value.
387 +/
388 IonErrorCode deserializeValueImpl(T : Decimal!maxW64bitSize, size_t maxW64bitSize)(scope IonDescribedValue data, scope ref T value)
389     pure @safe nothrow @nogc
390 {
391     IonErrorCode error;
392     auto ionValue = data.get!IonDecimal(error);
393     if (error)
394         return error;
395     auto ionDescribedDecimal = ionValue.get!IonDescribedDecimal(error);
396     if (error)
397         return error;
398     return ionDescribedDecimal.get(value);
399 }
400 
401 ///
402 version(mir_ion_test) unittest
403 {
404     import mir.ion.value;
405     import mir.ion.exception;
406     import mir.bignum.decimal;
407 
408     Decimal!128 value; // 256x64 bits
409 
410     // from ion decimal
411     auto data = IonValue([0x56, 0x00, 0xcb, 0x80, 0xbc, 0x2d, 0x86]).describe;
412 
413     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
414     assert(cast(double)value == -12332422e75);
415 }
416 
417 /++
418 Deserialize timestamp value.
419 +/
420 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
421     pure @safe nothrow @nogc
422     if (is(T == Timestamp))
423 {
424     if (_expect(data != null, true))
425     {
426         if (data.descriptor.type == IonTypeCode.timestamp)
427         {
428             return data.trustedGet!IonTimestamp.get!T(value);
429         }
430         else
431         {
432             IonErrorCode error;
433             auto ionValue = data.get!(const(char)[])(error);
434             if (!error && Timestamp.fromString(ionValue, value))
435                 return IonErrorCode.none;
436         }
437     }
438     return IonErrorCode.expectedTimestampValue;
439 }
440 
441 ///ditto
442 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
443     // pure @safe nothrow @nogc
444     if (!(hasProxy!T && !hasLikeStruct!T && isFirstOrderSerdeType!T) && is(typeof(Timestamp.init.opCast!T)))
445 {
446     Timestamp temporal;
447     if (auto error = .deserializeValueImpl(data, temporal))
448         return error;
449     value = temporal.opCast!T;
450     return IonErrorCode.none;
451 }
452 
453 ///
454 version(mir_ion_test) unittest
455 {
456     import mir.ion.value;
457     import mir.ion.exception;
458     import mir.bignum.decimal;
459 
460     Decimal!128 value; // 256x64 bits
461 
462     // from ion decimal
463     auto data = IonValue([0x56, 0x00, 0xcb, 0x80, 0xbc, 0x2d, 0x86]).describe;
464 
465     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
466     assert(cast(double)value == -12332422e75);
467 }
468 
469 package template hasProxy(T)
470 {
471     import mir.serde: serdeProxy;
472     static if (is(T == enum) || isAggregateType!T)
473         enum hasProxy = hasUDA!(T, serdeProxy);
474     else
475         enum hasProxy = false;
476 }
477 
478 package template hasLikeStruct(T)
479 {
480     import mir.serde: serdeLikeStruct;
481     static if (is(T == enum) || isAggregateType!T)
482         enum hasLikeStruct = hasUDA!(T, serdeLikeStruct);
483     else
484         enum hasLikeStruct = false;
485 }
486 
487 package template hasDiscriminatedField(T)
488 {
489     import mir.serde: serdeDiscriminatedField;
490     static if (is(T == enum) || isAggregateType!T)
491         enum hasDiscriminatedField = hasUDA!(T, serdeDiscriminatedField);
492     else
493         enum hasDiscriminatedField = false;
494 }
495 
496 package template hasFallbackStruct(T)
497 {
498     import mir.serde: serdeFallbackStruct;
499     static if (is(T == enum) || isAggregateType!T)
500         enum hasFallbackStruct = hasUDA!(T, serdeFallbackStruct);
501     else
502         enum hasFallbackStruct = false;
503 }
504 
505 /++
506 Deserialize struct/class value with proxy.
507 +/
508 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
509     if (hasProxy!T && !hasLikeStruct!T && isFirstOrderSerdeType!T)
510 {
511     import std.traits: Select;
512     import mir.serde: serdeGetProxy, serdeScoped, serdeScoped;
513     import mir.conv: to;
514 
515     serdeGetProxy!T proxy;
516     if (auto error = deserializeValueImpl(data, proxy))
517         return error;
518     value = proxy.to!T;
519     return IonErrorCode.none;
520 }
521 
522 /++
523 Deserialize enum value.
524 +/
525 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
526     if (is(T == enum) && !hasProxy!T)
527 {
528     import mir.serde: serdeParseEnum;
529     IonErrorCode error;
530     auto ionValue = data.get!(const(char)[])(error);
531     if (error)
532         return error;
533     if (serdeParseEnum(ionValue, value))
534         return IonErrorCode.none;
535     return IonErrorCode.expectedEnumValue;
536 }
537 
538 ///
539 version(mir_ion_test) unittest
540 {
541     import mir.ion.value;
542     import mir.ion.exception;
543     enum E {a, b, c}
544 
545     // from ion string
546     auto data = IonValue([0x81, 'b']).describe;
547     E value;
548 
549     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
550     assert(value == E.b);
551 }
552 
553 
554 /++
555 Deserialize ascii value from ion string.
556 +/
557 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
558     pure @safe nothrow
559     if (is(T == char))
560 {
561     IonErrorCode error;
562     auto ionValue = data.get!(const(char)[])(error);
563     if (error)
564         return error;
565     if (_expect(ionValue.length != 1, false))
566         return IonErrorCode.expectedCharValue; 
567     value = ionValue[0];
568     return IonErrorCode.none; 
569 }
570 
571 ///
572 version(mir_ion_test) unittest
573 {
574     import mir.ion.value;
575     import mir.ion.exception;
576 
577     auto data = IonValue([0x81, 'b']).describe;
578     char value;
579 
580     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
581     assert(value == 'b');
582 }
583 
584 private IonErrorCode deserializeListToScopedBuffer(Buffer)(scope IonDescribedValue data, ref Buffer buffer)
585 {
586     auto ionValue = data.trustedGet!IonList;
587     foreach (IonErrorCode error, scope IonDescribedValue ionElem; ionValue)
588     {
589         import std.traits: Unqual;
590         if (_expect(error, false))
591             return error;
592         Unqual!(typeof(buffer.data[0])) value;
593         error = deserializeValueImpl(ionElem, value);
594         if (_expect(error, false))
595             return error;
596         import core.lifetime: move;
597         buffer.put(move(value));
598     }
599     return IonErrorCode.none;
600 }
601 
602 
603 ///
604 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
605     if (is(T == E[], E) && !isSomeChar!E)
606 {
607     alias E = Unqual!(ForeachType!T);
608     if (data.descriptor.type == IonTypeCode.list)
609     {
610         import std.array: std_appender = appender;
611         auto buffer = std_appender!(E[]);
612         if (auto error = deserializeListToScopedBuffer(data, buffer))
613             return error;
614         value = buffer.data;
615         return IonErrorCode.none;
616     }
617     else
618     if (data.descriptor.type == IonTypeCode.null_)
619     {
620         value = null;
621         return IonErrorCode.none;
622     }
623     return IonErrorCode.expectedListValue;
624 }
625 
626 ///
627 @safe pure
628 version(mir_ion_test) unittest
629 {
630     import mir.ion.value;
631     import mir.ion.exception;
632 
633     auto data = IonValue([
634         0xbe, 0x91, 0x00, 0x00, 0x21, 0x0c,
635         0x00, 0x00, 0x48, 0x43, 0x0c, 0x6b,
636         0xf5, 0x26, 0x34, 0x00, 0x00, 0x00,
637         0x00]).describe;
638 
639     double[] value;
640     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
641     assert(value == [12, 100e13]);
642 }
643 
644 ///
645 IonErrorCode deserializeValueImpl(T)(scope IonDescribedValue data, scope ref T value)
646     if (is(T == RCArray!E, E) && !isSomeChar!E)
647 {
648     alias E = Unqual!(ForeachType!T);
649     if (data.descriptor.type == IonTypeCode.list)
650     {
651         import core.lifetime: move;
652         import std.traits: TemplateArgsOf;
653         auto buffer = scopedBuffer!E;
654         if (auto error = deserializeListToScopedBuffer(data, buffer))
655             return error;
656         auto ar = RCArray!E(buffer.length, false);
657         () @trusted {
658             buffer.moveDataAndEmplaceTo(ar[]);
659         } ();
660         static if (__traits(compiles, value = move(ar)))
661             value = move(ar);
662         else () @trusted {
663             value = ar.opCast!T;
664         } ();
665         return IonErrorCode.none;
666     }
667     else
668     if (data.descriptor.type == IonTypeCode.null_)
669     {
670         value = null;
671         return IonErrorCode.none;
672     }
673     return IonErrorCode.expectedListValue;
674 }
675 
676 ///
677 @safe pure
678 version(mir_ion_test) unittest
679 {
680     import mir.ion.exception;
681     import mir.ion.value;
682     import mir.rc.array: RCArray;
683 
684     auto data = IonValue([
685         0xbe, 0x91, 0x00, 0x00, 0x21, 0x0c,
686         0x00, 0x00, 0x48, 0x43, 0x0c, 0x6b,
687         0xf5, 0x26, 0x34, 0x00, 0x00, 0x00,
688         0x00]).describe;
689 
690     RCArray!(const double) value;
691     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
692     assert(value[] == [12, 100e13]);
693 }
694 
695 ///
696 IonErrorCode deserializeValueImpl(T : SmallArray!(E, maxLength), E, size_t maxLength)(scope IonDescribedValue data, out T value)
697 {
698     if (data.descriptor.type == IonTypeCode.list)
699     {
700         foreach (IonErrorCode error, scope IonDescribedValue ionElem; data.trustedGet!IonList)
701         {
702             if (_expect(error, false))
703                 return error;
704             if (value._length == maxLength)
705                 return IonErrorCode.smallArrayOverflow;
706             E elem;
707             error = .deserializeValueImpl(ionElem, elem);
708             if (_expect(error, false))
709                 return error;
710             import core.lifetime: move;
711             value.trustedAppend(move(elem));
712         }
713         return IonErrorCode.none;
714     }
715     else
716     if (data.descriptor.type == IonTypeCode.null_)
717     {
718         return IonErrorCode.none;
719     }
720     return IonErrorCode.expectedListValue;
721 }
722 
723 ///
724 IonErrorCode deserializeValueImpl(T : E[N], E, size_t N)(scope IonDescribedValue data, out T value)
725 {
726     if (data.descriptor.type == IonTypeCode.list)
727     {
728         size_t i;
729         foreach (IonErrorCode error, scope IonDescribedValue ionElem; data.trustedGet!IonList)
730         {
731             if (_expect(error, false))
732                 return error;
733             if (i >= N)
734                 return IonErrorCode.tooManyElementsForStaticArray;
735             error = .deserializeValueImpl(ionElem, value[i++]);
736             if (_expect(error, false))
737                 return error;
738         }
739         if (i < N)
740             return IonErrorCode.notEnoughElementsForStaticArray;
741         return IonErrorCode.none;
742     }
743     return IonErrorCode.expectedListValue;
744 }
745 
746 ///
747 @safe pure
748 version(mir_ion_test) unittest
749 {
750     import mir.ion.value;
751     import mir.ion.exception;
752     import mir.small_array;
753 
754     auto data = IonValue([
755         0xbe, 0x91, 0x00, 0x00, 0x21, 0x0c,
756         0x00, 0x00, 0x48, 0x43, 0x0c, 0x6b,
757         0xf5, 0x26, 0x34, 0x00, 0x00, 0x00,
758         0x00]).describe;
759     
760     SmallArray!(double, 3) value;
761     assert(deserializeValueImpl(data, value) == IonErrorCode.none);
762     assert(value == [12, 100e13]);
763 }