-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
json.dart
1194 lines (1095 loc) · 39.7 KB
/
json.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.convert;
/// Error thrown by JSON serialization if an object cannot be serialized.
///
/// The [unsupportedObject] field holds that object that failed to be serialized.
///
/// If an object isn't directly serializable, the serializer calls the `toJson`
/// method on the object. If that call fails, the error will be stored in the
/// [cause] field. If the call returns an object that isn't directly
/// serializable, the [cause] is null.
class JsonUnsupportedObjectError extends Error {
/// The object that could not be serialized.
final Object? unsupportedObject;
/// The exception thrown when trying to convert the object.
final Object? cause;
/// The partial result of the conversion, up until the error happened.
///
/// May be null.
final String? partialResult;
JsonUnsupportedObjectError(this.unsupportedObject,
{this.cause, this.partialResult});
String toString() {
var safeString = Error.safeToString(unsupportedObject);
String prefix;
if (cause != null) {
prefix = "Converting object to an encodable object failed:";
} else {
prefix = "Converting object did not return an encodable object:";
}
return "$prefix $safeString";
}
}
/// Reports that an object could not be stringified due to cyclic references.
///
/// An object that references itself cannot be serialized by
/// [JsonCodec.encode]/[JsonEncoder.convert].
/// When the cycle is detected, a [JsonCyclicError] is thrown.
class JsonCyclicError extends JsonUnsupportedObjectError {
/// The first object that was detected as part of a cycle.
JsonCyclicError(Object? object) : super(object);
String toString() => "Cyclic error in JSON stringify";
}
/// An instance of the default implementation of the [JsonCodec].
///
/// This instance provides a convenient access to the most common JSON
/// use cases.
///
/// Examples:
/// ```dart
/// var encoded = json.encode([1, 2, { "a": null }]);
/// var decoded = json.decode('["foo", { "bar": 499 }]');
/// ```
/// The top-level [jsonEncode] and [jsonDecode] functions may be used instead if
/// a local variable shadows the [json] constant.
const JsonCodec json = JsonCodec();
/// Converts [object] to a JSON string.
///
/// If value contains objects that are not directly encodable to a JSON
/// string (a value that is not a number, boolean, string, null, list or a map
/// with string keys), the [toEncodable] function is used to convert it to an
/// object that must be directly encodable.
///
/// If [toEncodable] is omitted, it defaults to a function that returns the
/// result of calling `.toJson()` on the unencodable object.
///
/// Shorthand for `json.encode`. Useful if a local variable shadows the global
/// [json] constant.
///
/// Example:
/// ```dart
/// const data = {'text': 'foo', 'value': 2, 'status': false, 'extra': null};
/// final String jsonString = jsonEncode(data);
/// print(jsonString); // {"text":"foo","value":2,"status":false,"extra":null}
/// ```
///
/// Example of converting an otherwise unsupported object to a
/// custom JSON format:
///
/// ```dart
/// class CustomClass {
/// final String text;
/// final int value;
/// CustomClass({required this.text, required this.value});
/// CustomClass.fromJson(Map<String, dynamic> json)
/// : text = json['text'],
/// value = json['value'];
///
/// static Map<String, dynamic> toJson(CustomClass value) =>
/// {'text': value.text, 'value': value.value};
/// }
///
/// void main() {
/// final CustomClass cc = CustomClass(text: 'Dart', value: 123);
/// final jsonText = jsonEncode({'cc': cc},
/// toEncodable: (Object? value) => value is CustomClass
/// ? CustomClass.toJson(value)
/// : throw UnsupportedError('Cannot convert to JSON: $value'));
/// print(jsonText); // {"cc":{"text":"Dart","value":123}}
/// }
/// ```
String jsonEncode(Object? object,
{Object? toEncodable(Object? nonEncodable)?}) =>
json.encode(object, toEncodable: toEncodable);
/// Parses the string and returns the resulting Json object.
///
/// The optional [reviver] function is called once for each object or list
/// property that has been parsed during decoding. The `key` argument is either
/// the integer list index for a list property, the string map key for object
/// properties, or `null` for the final result.
///
/// The default [reviver] (when not provided) is the identity function.
///
/// Shorthand for `json.decode`. Useful if a local variable shadows the global
/// [json] constant.
///
/// Example:
/// ```dart
/// const jsonString =
/// '{"text": "foo", "value": 1, "status": false, "extra": null}';
///
/// final data = jsonDecode(jsonString);
/// print(data['text']); // foo
/// print(data['value']); // 1
/// print(data['status']); // false
/// print(data['extra']); // null
///
/// const jsonArray = '''
/// [{"text": "foo", "value": 1, "status": true},
/// {"text": "bar", "value": 2, "status": false}]
/// ''';
///
/// final List<dynamic> dataList = jsonDecode(jsonArray);
/// print(dataList[0]); // {text: foo, value: 1, status: true}
/// print(dataList[1]); // {text: bar, value: 2, status: false}
///
/// final item = dataList[0];
/// print(item['text']); // foo
/// print(item['value']); // 1
/// print(item['status']); // false
/// ```
dynamic jsonDecode(String source,
{Object? reviver(Object? key, Object? value)?}) =>
json.decode(source, reviver: reviver);
/// A [JsonCodec] encodes JSON objects to strings and decodes strings to
/// JSON objects.
///
/// Examples:
/// ```dart
/// var encoded = json.encode([1, 2, { "a": null }]);
/// var decoded = json.decode('["foo", { "bar": 499 }]');
/// ```
final class JsonCodec extends Codec<Object?, String> {
final Object? Function(Object? key, Object? value)? _reviver;
final Object? Function(dynamic)? _toEncodable;
/// Creates a `JsonCodec` with the given reviver and encoding function.
///
/// The [reviver] function is called during decoding. It is invoked once for
/// each object or list property that has been parsed.
/// The `key` argument is either the integer list index for a list property,
/// the string map key for object properties, or `null` for the final result.
///
/// If [reviver] is omitted, it defaults to returning the value argument.
///
/// The [toEncodable] function is used during encoding. It is invoked for
/// values that are not directly encodable to a string (a value that is not a
/// number, boolean, string, null, list or a map with string keys). The
/// function must return an object that is directly encodable. The elements of
/// a returned list and values of a returned map do not need to be directly
/// encodable, and if they aren't, `toEncodable` will be used on them as well.
/// Please notice that it is possible to cause an infinite recursive regress
/// in this way, by effectively creating an infinite data structure through
/// repeated call to `toEncodable`.
///
/// If [toEncodable] is omitted, it defaults to a function that returns the
/// result of calling `.toJson()` on the unencodable object.
const JsonCodec(
{Object? reviver(Object? key, Object? value)?,
Object? toEncodable(dynamic object)?})
: _reviver = reviver,
_toEncodable = toEncodable;
/// Creates a `JsonCodec` with the given reviver.
///
/// The [reviver] function is called once for each object or list property
/// that has been parsed during decoding. The `key` argument is either the
/// integer list index for a list property, the string map key for object
/// properties, or `null` for the final result.
JsonCodec.withReviver(dynamic reviver(Object? key, Object? value))
: this(reviver: reviver);
/// Parses the string and returns the resulting Json object.
///
/// The optional [reviver] function is called once for each object or list
/// property that has been parsed during decoding. The `key` argument is either
/// the integer list index for a list property, the string map key for object
/// properties, or `null` for the final result.
///
/// The default [reviver] (when not provided) is the identity function.
dynamic decode(String source,
{Object? reviver(Object? key, Object? value)?}) {
reviver ??= _reviver;
if (reviver == null) return decoder.convert(source);
return JsonDecoder(reviver).convert(source);
}
/// Converts [value] to a JSON string.
///
/// If value contains objects that are not directly encodable to a JSON
/// string (a value that is not a number, boolean, string, null, list or a map
/// with string keys), the [toEncodable] function is used to convert it to an
/// object that must be directly encodable.
///
/// If [toEncodable] is omitted, it defaults to a function that returns the
/// result of calling `.toJson()` on the unencodable object.
String encode(Object? value, {Object? toEncodable(dynamic object)?}) {
toEncodable ??= _toEncodable;
if (toEncodable == null) return encoder.convert(value);
return JsonEncoder(toEncodable).convert(value);
}
JsonEncoder get encoder {
if (_toEncodable == null) return const JsonEncoder();
return JsonEncoder(_toEncodable);
}
JsonDecoder get decoder {
if (_reviver == null) return const JsonDecoder();
return JsonDecoder(_reviver);
}
}
/// This class converts JSON objects to strings.
///
/// Example:
///
/// ```dart
/// const JsonEncoder encoder = JsonEncoder();
/// const data = {'text': 'foo', 'value': '2'};
///
/// final String jsonString = encoder.convert(data);
/// print(jsonString); // {"text":"foo","value":"2"}
/// ```
///
/// Example of pretty-printed output:
///
/// ```dart
/// const JsonEncoder encoder = JsonEncoder.withIndent(' ');
///
/// const data = {'text': 'foo', 'value': '2'};
/// final String jsonString = encoder.convert(data);
/// print(jsonString);
/// // {
/// // "text": "foo",
/// // "value": "2"
/// // }
/// ```
final class JsonEncoder extends Converter<Object?, String> {
/// The string used for indention.
///
/// When generating multi-line output, this string is inserted once at the
/// beginning of each indented line for each level of indentation.
///
/// If `null`, the output is encoded as a single line.
final String? indent;
/// Function called on non-encodable objects to return a replacement
/// encodable object that will be encoded in the orignal's place.
final Object? Function(dynamic)? _toEncodable;
/// Creates a JSON encoder.
///
/// The JSON encoder handles numbers, strings, booleans, null, lists and
/// maps with string keys directly.
///
/// Any other object is attempted converted by [toEncodable] to an
/// object that is of one of the convertible types.
///
/// If [toEncodable] is omitted, it defaults to calling `.toJson()` on
/// the object.
const JsonEncoder([Object? toEncodable(dynamic object)?])
: indent = null,
_toEncodable = toEncodable;
/// Creates a JSON encoder that creates multi-line JSON.
///
/// The encoding of elements of lists and maps are indented and put on separate
/// lines. The [indent] string is prepended to these elements, once for each
/// level of indentation.
///
/// If [indent] is `null`, the output is encoded as a single line.
///
/// The JSON encoder handles numbers, strings, booleans, null, lists and
/// maps with string keys directly.
///
/// Any other object is attempted converted by [toEncodable] to an
/// object that is of one of the convertible types.
///
/// If [toEncodable] is omitted, it defaults to calling `.toJson()` on
/// the object.
const JsonEncoder.withIndent(this.indent,
[Object? toEncodable(dynamic object)?])
: _toEncodable = toEncodable;
/// Converts [object] to a JSON [String].
///
/// Directly serializable values are [num], [String], [bool], and [Null], as
/// well as some [List] and [Map] values. For [List], the elements must all be
/// serializable. For [Map], the keys must be [String] and the values must be
/// serializable.
///
/// If a value of any other type is attempted to be serialized, the
/// `toEncodable` function provided in the constructor is called with the value
/// as argument. The result, which must be a directly serializable value, is
/// serialized instead of the original value.
///
/// If the conversion throws, or returns a value that is not directly
/// serializable, a [JsonUnsupportedObjectError] exception is thrown.
/// If the call throws, the error is caught and stored in the
/// [JsonUnsupportedObjectError]'s `cause` field.
///
/// If a [List] or [Map] contains a reference to itself, directly or through
/// other lists or maps, it cannot be serialized and a [JsonCyclicError] is
/// thrown.
///
/// [object] should not change during serialization.
///
/// If an object is serialized more than once, [convert] may cache the text
/// for it. In other words, if the content of an object changes after it is
/// first serialized, the new values may not be reflected in the result.
String convert(Object? object) =>
_JsonStringStringifier.stringify(object, _toEncodable, indent);
/// Starts a chunked conversion.
///
/// The converter works more efficiently if the given [sink] is a
/// [StringConversionSink].
///
/// Returns a chunked-conversion sink that accepts at most one object. It is
/// an error to invoke `add` more than once on the returned sink.
ChunkedConversionSink<Object?> startChunkedConversion(Sink<String> sink) {
if (sink is _Utf8EncoderSink) {
return _JsonUtf8EncoderSink(
sink._sink,
_toEncodable,
JsonUtf8Encoder._utf8Encode(indent),
JsonUtf8Encoder._defaultBufferSize);
}
return _JsonEncoderSink(
sink is StringConversionSink ? sink : StringConversionSink.from(sink),
_toEncodable,
indent);
}
// Override the base class's bind, to provide a better type.
Stream<String> bind(Stream<Object?> stream) => super.bind(stream);
Converter<Object?, T> fuse<T>(Converter<String, T> other) {
if (other is Utf8Encoder) {
// The instance check guarantees that `T` is (a subtype of) List<int>,
// but the static type system doesn't know that, and so we cast.
return JsonUtf8Encoder(indent, _toEncodable) as Converter<Object?, T>;
}
return super.fuse<T>(other);
}
}
/// Encoder that encodes a single object as a UTF-8 encoded JSON string.
///
/// This encoder works equivalently to first converting the object to
/// a JSON string, and then UTF-8 encoding the string, but without
/// creating an intermediate string.
final class JsonUtf8Encoder extends Converter<Object?, List<int>> {
/// Default buffer size used by the JSON-to-UTF-8 encoder.
static const int _defaultBufferSize = 256;
/// Indentation used in pretty-print mode, `null` if not pretty.
final List<int>? _indent;
/// Function called with each un-encodable object encountered.
final Object? Function(dynamic)? _toEncodable;
/// UTF-8 buffer size.
final int _bufferSize;
/// Create converter.
///
/// If [indent] is non-`null`, the converter attempts to "pretty-print" the
/// JSON, and uses `indent` as the indentation. Otherwise the result has no
/// whitespace outside of string literals.
/// If `indent` contains characters that are not valid JSON whitespace
/// characters, the result will not be valid JSON. JSON whitespace characters
/// are space (U+0020), tab (U+0009), line feed (U+000a) and carriage return
/// (U+000d) ([ECMA
/// 404](http://www.ecma-international.org/publications/standards/Ecma-404.htm)).
///
/// The [bufferSize] is the size of the internal buffers used to collect
/// UTF-8 code units.
/// If using [startChunkedConversion], it will be the size of the chunks.
///
/// The JSON encoder handles numbers, strings, booleans, null, lists and maps
/// directly.
///
/// Any other object is attempted converted by [toEncodable] to an object that
/// is of one of the convertible types.
///
/// If [toEncodable] is omitted, it defaults to calling `.toJson()` on the
/// object.
JsonUtf8Encoder(
[String? indent, dynamic toEncodable(dynamic object)?, int? bufferSize])
: _indent = _utf8Encode(indent),
_toEncodable = toEncodable,
_bufferSize = bufferSize ?? _defaultBufferSize;
static List<int>? _utf8Encode(String? string) {
if (string == null) return null;
if (string.isEmpty) return Uint8List(0);
checkAscii:
{
for (var i = 0; i < string.length; i++) {
if (string.codeUnitAt(i) >= 0x80) break checkAscii;
}
return string.codeUnits;
}
return utf8.encode(string);
}
/// Convert [object] into UTF-8 encoded JSON.
List<int> convert(Object? object) {
var bytes = <List<int>>[];
// The `stringify` function always converts into chunks.
// Collect the chunks into the `bytes` list, then combine them afterwards.
void addChunk(Uint8List chunk, int start, int end) {
if (start > 0 || end < chunk.length) {
var length = end - start;
chunk =
Uint8List.view(chunk.buffer, chunk.offsetInBytes + start, length);
}
bytes.add(chunk);
}
_JsonUtf8Stringifier.stringify(
object, _indent, _toEncodable, _bufferSize, addChunk);
if (bytes.length == 1) return bytes[0];
var length = 0;
for (var i = 0; i < bytes.length; i++) {
length += bytes[i].length;
}
var result = Uint8List(length);
for (var i = 0, offset = 0; i < bytes.length; i++) {
var byteList = bytes[i];
int end = offset + byteList.length;
result.setRange(offset, end, byteList);
offset = end;
}
return result;
}
/// Start a chunked conversion.
///
/// Only one object can be passed into the returned sink.
///
/// The argument [sink] will receive byte lists in sizes depending on the
/// `bufferSize` passed to the constructor when creating this encoder.
ChunkedConversionSink<Object?> startChunkedConversion(Sink<List<int>> sink) {
ByteConversionSink byteSink;
if (sink is ByteConversionSink) {
byteSink = sink;
} else {
byteSink = ByteConversionSink.from(sink);
}
return _JsonUtf8EncoderSink(byteSink, _toEncodable, _indent, _bufferSize);
}
// Override the base class's bind, to provide a better type.
Stream<List<int>> bind(Stream<Object?> stream) {
return super.bind(stream);
}
}
/// Implements the chunked conversion from object to its JSON representation.
///
/// The sink only accepts one value, but will produce output in a chunked way.
class _JsonEncoderSink extends ChunkedConversionSink<Object?> {
final String? _indent;
final Object? Function(dynamic)? _toEncodable;
final StringConversionSink _sink;
bool _isDone = false;
_JsonEncoderSink(this._sink, this._toEncodable, this._indent);
/// Encodes the given object [o].
///
/// It is an error to invoke this method more than once on any instance. While
/// this makes the input effectively non-chunked the output will be generated
/// in a chunked way.
void add(Object? o) {
if (_isDone) {
throw StateError("Only one call to add allowed");
}
_isDone = true;
var stringSink = _sink.asStringSink();
_JsonStringStringifier.printOn(o, stringSink, _toEncodable, _indent);
stringSink.close();
}
void close() {/* do nothing */}
}
/// Sink returned when starting a chunked conversion from object to bytes.
class _JsonUtf8EncoderSink extends ChunkedConversionSink<Object?> {
/// The byte sink receiving the encoded chunks.
final ByteConversionSink _sink;
final List<int>? _indent;
final Object? Function(dynamic)? _toEncodable;
final int _bufferSize;
bool _isDone = false;
_JsonUtf8EncoderSink(
this._sink, this._toEncodable, this._indent, this._bufferSize);
/// Callback called for each slice of result bytes.
void _addChunk(Uint8List chunk, int start, int end) {
_sink.addSlice(chunk, start, end, false);
}
void add(Object? object) {
if (_isDone) {
throw StateError("Only one call to add allowed");
}
_isDone = true;
_JsonUtf8Stringifier.stringify(
object, _indent, _toEncodable, _bufferSize, _addChunk);
_sink.close();
}
void close() {
if (!_isDone) {
_isDone = true;
_sink.close();
}
}
}
/// This class parses JSON strings and builds the corresponding objects.
///
/// A JSON input must be the JSON encoding of a single JSON value,
/// which can be a list or map containing other values.
///
/// Throws [FormatException] if the input is not valid JSON text.
///
/// Example:
/// ```dart
/// const JsonDecoder decoder = JsonDecoder();
///
/// const String jsonString = '''
/// {
/// "data": [{"text": "foo", "value": 1 },
/// {"text": "bar", "value": 2 }],
/// "text": "Dart"
/// }
/// ''';
///
/// final Map<String, dynamic> object = decoder.convert(jsonString);
///
/// final item = object['data'][0];
/// print(item['text']); // foo
/// print(item['value']); // 1
///
/// print(object['text']); // Dart
/// ```
///
/// When used as a [StreamTransformer], the input stream may emit
/// multiple strings. The concatenation of all of these strings must
/// be a valid JSON encoding of a single JSON value.
final class JsonDecoder extends Converter<String, Object?> {
final Object? Function(Object? key, Object? value)? _reviver;
/// Constructs a new JsonDecoder.
///
/// The [reviver] may be `null`.
const JsonDecoder([Object? reviver(Object? key, Object? value)?])
: _reviver = reviver;
/// Converts the given JSON-string [input] to its corresponding object.
///
/// Parsed JSON values are of the types [num], [String], [bool], [Null],
/// [List]s of parsed JSON values or [Map]s from [String] to parsed JSON
/// values.
///
/// If `this` was initialized with a reviver, then the parsing operation
/// invokes the reviver on every object or list property that has been parsed.
/// The arguments are the property name ([String]) or list index ([int]), and
/// the value is the parsed value. The return value of the reviver is used as
/// the value of that property instead the parsed value.
///
/// Throws [FormatException] if the input is not valid JSON text.
dynamic convert(String input) => _parseJson(input, _reviver);
/// Starts a conversion from a chunked JSON string to its corresponding object.
///
/// The output [sink] receives exactly one decoded element through `add`.
external StringConversionSink startChunkedConversion(Sink<Object?> sink);
// Override the base class's bind, to provide a better type.
Stream<Object?> bind(Stream<String> stream) => super.bind(stream);
}
// Internal optimized JSON parsing implementation.
external dynamic _parseJson(String source, reviver(key, value)?);
// Implementation of encoder/stringifier.
// ignore: avoid_dynamic_calls
dynamic _defaultToEncodable(dynamic object) => object.toJson();
/// JSON encoder that traverses an object structure and writes JSON source.
///
/// This is an abstract implementation that doesn't decide on the output
/// format, but writes the JSON through abstract methods like [writeString].
abstract class _JsonStringifier {
// Character code constants.
static const int backspace = 0x08;
static const int tab = 0x09;
static const int newline = 0x0a;
static const int carriageReturn = 0x0d;
static const int formFeed = 0x0c;
static const int quote = 0x22;
static const int char_0 = 0x30;
static const int backslash = 0x5c;
static const int char_b = 0x62;
static const int char_d = 0x64;
static const int char_f = 0x66;
static const int char_n = 0x6e;
static const int char_r = 0x72;
static const int char_t = 0x74;
static const int char_u = 0x75;
static const int surrogateMin = 0xd800;
static const int surrogateMask = 0xfc00;
static const int surrogateLead = 0xd800;
static const int surrogateTrail = 0xdc00;
/// List of objects currently being traversed. Used to detect cycles.
final List _seen = [];
/// Function called for each un-encodable object encountered.
final Function(dynamic) _toEncodable;
_JsonStringifier(dynamic toEncodable(dynamic o)?)
: _toEncodable = toEncodable ?? _defaultToEncodable;
String? get _partialResult;
/// Append a string to the JSON output.
void writeString(String characters);
/// Append part of a string to the JSON output.
void writeStringSlice(String characters, int start, int end);
/// Append a single character, given by its code point, to the JSON output.
void writeCharCode(int charCode);
/// Write a number to the JSON output.
void writeNumber(num number);
// ('0' + x) or ('a' + x - 10)
static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
/// Write, and suitably escape, a string's content as a JSON string literal.
void writeStringContent(String s) {
var offset = 0;
final length = s.length;
for (var i = 0; i < length; i++) {
var charCode = s.codeUnitAt(i);
if (charCode > backslash) {
if (charCode >= surrogateMin) {
// Possible surrogate. Check if it is unpaired.
if (((charCode & surrogateMask) == surrogateLead &&
!(i + 1 < length &&
(s.codeUnitAt(i + 1) & surrogateMask) ==
surrogateTrail)) ||
((charCode & surrogateMask) == surrogateTrail &&
!(i - 1 >= 0 &&
(s.codeUnitAt(i - 1) & surrogateMask) ==
surrogateLead))) {
// Lone surrogate.
if (i > offset) writeStringSlice(s, offset, i);
offset = i + 1;
writeCharCode(backslash);
writeCharCode(char_u);
writeCharCode(char_d);
writeCharCode(hexDigit((charCode >> 8) & 0xf));
writeCharCode(hexDigit((charCode >> 4) & 0xf));
writeCharCode(hexDigit(charCode & 0xf));
}
}
continue;
}
if (charCode < 32) {
if (i > offset) writeStringSlice(s, offset, i);
offset = i + 1;
writeCharCode(backslash);
switch (charCode) {
case backspace:
writeCharCode(char_b);
break;
case tab:
writeCharCode(char_t);
break;
case newline:
writeCharCode(char_n);
break;
case formFeed:
writeCharCode(char_f);
break;
case carriageReturn:
writeCharCode(char_r);
break;
default:
writeCharCode(char_u);
writeCharCode(char_0);
writeCharCode(char_0);
writeCharCode(hexDigit((charCode >> 4) & 0xf));
writeCharCode(hexDigit(charCode & 0xf));
break;
}
} else if (charCode == quote || charCode == backslash) {
if (i > offset) writeStringSlice(s, offset, i);
offset = i + 1;
writeCharCode(backslash);
writeCharCode(charCode);
}
}
if (offset == 0) {
writeString(s);
} else if (offset < length) {
writeStringSlice(s, offset, length);
}
}
/// Check if an encountered object is already being traversed.
///
/// Records the object if it isn't already seen. Should have a matching call to
/// [_removeSeen] when the object is no longer being traversed.
void _checkCycle(Object? object) {
for (var i = 0; i < _seen.length; i++) {
if (identical(object, _seen[i])) {
throw JsonCyclicError(object);
}
}
_seen.add(object);
}
/// Remove [object] from the list of currently traversed objects.
///
/// Should be called in the opposite order of the matching [_checkCycle]
/// calls.
void _removeSeen(Object? object) {
assert(_seen.isNotEmpty);
assert(identical(_seen.last, object));
_seen.removeLast();
}
/// Write an object.
///
/// If [object] isn't directly encodable, the [_toEncodable] function gets one
/// chance to return a replacement which is encodable.
void writeObject(Object? object) {
// Tries stringifying object directly. If it's not a simple value, List or
// Map, call toJson() to get a custom representation and try serializing
// that.
if (writeJsonValue(object)) return;
_checkCycle(object);
try {
var customJson = _toEncodable(object);
if (!writeJsonValue(customJson)) {
throw JsonUnsupportedObjectError(object, partialResult: _partialResult);
}
_removeSeen(object);
} catch (e) {
throw JsonUnsupportedObjectError(object,
cause: e, partialResult: _partialResult);
}
}
/// Serialize a [num], [String], [bool], [Null], [List] or [Map] value.
///
/// Returns true if the value is one of these types, and false if not.
/// If a value is both a [List] and a [Map], it's serialized as a [List].
bool writeJsonValue(Object? object) {
if (object is num) {
if (!object.isFinite) return false;
writeNumber(object);
return true;
} else if (identical(object, true)) {
writeString('true');
return true;
} else if (identical(object, false)) {
writeString('false');
return true;
} else if (object == null) {
writeString('null');
return true;
} else if (object is String) {
writeString('"');
writeStringContent(object);
writeString('"');
return true;
} else if (object is List) {
_checkCycle(object);
writeList(object);
_removeSeen(object);
return true;
} else if (object is Map) {
_checkCycle(object);
// writeMap can fail if keys are not all strings.
var success = writeMap(object);
_removeSeen(object);
return success;
} else {
return false;
}
}
/// Serialize a [List].
void writeList(List<Object?> list) {
writeString('[');
if (list.isNotEmpty) {
writeObject(list[0]);
for (var i = 1; i < list.length; i++) {
writeString(',');
writeObject(list[i]);
}
}
writeString(']');
}
/// Serialize a [Map].
bool writeMap(Map<Object?, Object?> map) {
if (map.isEmpty) {
writeString("{}");
return true;
}
var keyValueList = List<Object?>.filled(map.length * 2, null);
var i = 0;
var allStringKeys = true;
map.forEach((key, value) {
if (key is! String) {
allStringKeys = false;
}
keyValueList[i++] = key;
keyValueList[i++] = value;
});
if (!allStringKeys) return false;
writeString('{');
var separator = '"';
for (var i = 0; i < keyValueList.length; i += 2) {
writeString(separator);
separator = ',"';
writeStringContent(keyValueList[i] as String);
writeString('":');
writeObject(keyValueList[i + 1]);
}
writeString('}');
return true;
}
}
/// A modification of [_JsonStringifier] which indents the contents of [List] and
/// [Map] objects using the specified indent value.
///
/// Subclasses should implement [writeIndentation].
mixin _JsonPrettyPrintMixin implements _JsonStringifier {
int _indentLevel = 0;
/// Add [indentLevel] indentations to the JSON output.
void writeIndentation(int indentLevel);
void writeList(List<Object?> list) {
if (list.isEmpty) {
writeString('[]');
} else {
writeString('[\n');
_indentLevel++;
writeIndentation(_indentLevel);
writeObject(list[0]);
for (var i = 1; i < list.length; i++) {
writeString(',\n');
writeIndentation(_indentLevel);
writeObject(list[i]);
}
writeString('\n');
_indentLevel--;
writeIndentation(_indentLevel);
writeString(']');
}
}
bool writeMap(Map<Object?, Object?> map) {
if (map.isEmpty) {
writeString("{}");
return true;
}
var keyValueList = List<Object?>.filled(map.length * 2, null);
var i = 0;
var allStringKeys = true;
map.forEach((key, value) {
if (key is! String) {
allStringKeys = false;
}
keyValueList[i++] = key;
keyValueList[i++] = value;
});
if (!allStringKeys) return false;
writeString('{\n');
_indentLevel++;
var separator = "";
for (var i = 0; i < keyValueList.length; i += 2) {
writeString(separator);
separator = ",\n";
writeIndentation(_indentLevel);
writeString('"');
writeStringContent(keyValueList[i] as String);
writeString('": ');
writeObject(keyValueList[i + 1]);
}
writeString('\n');
_indentLevel--;
writeIndentation(_indentLevel);
writeString('}');
return true;
}
}
/// A specialization of [_JsonStringifier] that writes its JSON to a string.
class _JsonStringStringifier extends _JsonStringifier {
final StringSink _sink;
_JsonStringStringifier(
this._sink, dynamic Function(dynamic object)? _toEncodable)
: super(_toEncodable);
/// Convert object to a string.
///
/// The [toEncodable] function is used to convert non-encodable objects
/// to encodable ones.
///
/// If [indent] is not `null`, the resulting JSON will be "pretty-printed"
/// with newlines and indentation. The `indent` string is added as indentation
/// for each indentation level. It should only contain valid JSON whitespace
/// characters (space, tab, carriage return or line feed).
static String stringify(
Object? object, dynamic toEncodable(dynamic object)?, String? indent) {
var output = StringBuffer();
printOn(object, output, toEncodable, indent);
return output.toString();
}
/// Convert object to a string, and write the result to the [output] sink.
///
/// The result is written piecemally to the sink.
static void printOn(Object? object, StringSink output,
dynamic toEncodable(dynamic o)?, String? indent) {
_JsonStringifier stringifier;
if (indent == null) {
stringifier = _JsonStringStringifier(output, toEncodable);
} else {
stringifier = _JsonStringStringifierPretty(output, toEncodable, indent);
}
stringifier.writeObject(object);
}
String? get _partialResult => _sink is StringBuffer ? _sink.toString() : null;
void writeNumber(num number) {
_sink.write(number.toString());
}
void writeString(String string) {
_sink.write(string);
}
void writeStringSlice(String string, int start, int end) {
_sink.write(string.substring(start, end));
}
void writeCharCode(int charCode) {
_sink.writeCharCode(charCode);