OLD | NEW |
---|---|
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Utilities for encoding and decoding JSON (JavaScript Object Notation) data. | 6 * Utilities for encoding and decoding JSON (JavaScript Object Notation) data. |
7 */ | 7 */ |
8 | 8 |
9 library dart.json; | 9 library dart.json; |
10 | 10 |
11 import "dart:convert"; | 11 import "dart:convert"; |
12 import "dart:collection" show HashSet; | |
12 export "dart:convert" show JsonUnsupportedObjectError, JsonCyclicError; | 13 export "dart:convert" show JsonUnsupportedObjectError, JsonCyclicError; |
13 | 14 |
14 // JSON parsing and serialization. | 15 // JSON parsing and serialization. |
15 | 16 |
16 /** | 17 /** |
17 * Parses [json] and build the corresponding parsed JSON value. | 18 * Parses [json] and build the corresponding parsed JSON value. |
18 * | 19 * |
19 * Parsed JSON values are of the types [num], [String], [bool], [Null], | 20 * Parsed JSON values are of the types [num], [String], [bool], [Null], |
20 * [List]s of parsed JSON values or [Map]s from [String] to parsed | 21 * [List]s of parsed JSON values or [Map]s from [String] to parsed |
21 * JSON values. | 22 * JSON values. |
(...skipping 15 matching lines...) Expand all Loading... | |
37 } | 38 } |
38 | 39 |
39 /** | 40 /** |
40 * Serializes [object] into a JSON string. | 41 * Serializes [object] into a JSON string. |
41 * | 42 * |
42 * Directly serializable values are [num], [String], [bool], and [Null], as well | 43 * Directly serializable values are [num], [String], [bool], and [Null], as well |
43 * as some [List] and [Map] values. | 44 * as some [List] and [Map] values. |
44 * For [List], the elements must all be serializable. | 45 * For [List], the elements must all be serializable. |
45 * For [Map], the keys must be [String] and the values must be serializable. | 46 * For [Map], the keys must be [String] and the values must be serializable. |
46 * | 47 * |
47 * If a value is any other type is attempted serialized, a "toJson()" method | 48 * If a value is any other type is attempted serialized, the [convert] method |
48 * is invoked on the object and the result, which must be a directly | 49 * is called with the object as argument, and the result, which must be a |
49 * serializable value, is serialized instead of the original value. | 50 * directly serializable value, is serialized instead of the original value. |
51 * If [convert] is omitted, the default is to call `object.toJson()` on the | |
52 * object. | |
50 * | 53 * |
51 * If the object does not support this method, throws, or returns a | 54 * If the conversion throws, or returns a value that is not directly |
52 * value that is not directly serializable, a [JsonUnsupportedObjectError] | 55 * serializable, a [JsonUnsupportedObjectError] exception is thrown. |
53 * exception is thrown. If the call throws (including the case where there | 56 * If the call throws (including the case where there |
54 * is no nullary "toJson" method, the error is caught and stored in the | 57 * is no nullary "toJson" method), the error is caught and stored in the |
55 * [JsonUnsupportedObjectError]'s [:cause:] field. | 58 * [JsonUnsupportedObjectError]'s [:cause:] field. |
56 * | 59 * |
57 * If a [List] or [Map] contains a reference to itself, directly or through | 60 * If a [List] or [Map] contains a reference to itself, directly or through |
58 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is | 61 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is |
59 * thrown. | 62 * thrown. |
60 * | 63 * |
61 * Json Objects should not change during serialization. | 64 * The objects being serialized should not change during serialization. |
62 * If an object is serialized more than once, [stringify] is allowed to cache | 65 * If an object is serialized more than once, [stringify] is allowed to cache |
63 * the JSON text for it. I.e., if an object changes after it is first | 66 * the JSON text for it. I.e., if an object changes after it is first |
64 * serialized, the new values may or may not be reflected in the result. | 67 * serialized, the new values may or may not be reflected in the result. |
65 */ | 68 */ |
66 String stringify(Object object) { | 69 String stringify(Object object, [convert(object)]) { |
floitsch
2013/10/04 15:25:08
You don't have the latest version (which now has d
Lasse Reichstein Nielsen
2013/10/07 12:46:59
Merging .... please hold. :)
I don't want to supp
| |
67 return _JsonStringifier.stringify(object); | 70 if (convert == null) convert = _defaultConvert; |
71 return _JsonStringifier.stringify(object, convert); | |
68 } | 72 } |
69 | 73 |
70 /** | 74 /** |
71 * Serializes [object] into [output] stream. | 75 * Serializes [object] into [output] stream. |
72 * | 76 * |
73 * Performs the same operations as [stringify] but outputs the resulting | 77 * Performs the same operations as [stringify] but outputs the resulting |
74 * string to an existing [StringSink] instead of creating a new [String]. | 78 * string to an existing [StringSink] instead of creating a new [String]. |
75 * | 79 * |
76 * If serialization fails by throwing, some data might have been added to | 80 * If serialization fails by throwing, some data might have been added to |
77 * [output], but it won't contain valid JSON text. | 81 * [output], but it won't contain valid JSON text. |
78 */ | 82 */ |
79 void printOn(Object object, StringSink output) { | 83 void printOn(Object object, StringSink output, [ convert(object) ]) { |
80 return _JsonStringifier.printOn(object, output); | 84 if (convert == null) convert = _defaultConvert; |
85 return _JsonStringifier.printOn(object, output, convert); | |
81 } | 86 } |
82 | 87 |
83 //// Implementation /////////////////////////////////////////////////////////// | 88 //// Implementation /////////////////////////////////////////////////////////// |
84 | 89 |
85 // Simple API for JSON parsing. | 90 // Simple API for JSON parsing. |
86 | 91 |
87 abstract class JsonListener { | 92 abstract class JsonListener { |
88 void handleString(String value) {} | 93 void handleString(String value) {} |
89 void handleNumber(num value) {} | 94 void handleNumber(num value) {} |
90 void handleBool(bool value) {} | 95 void handleBool(bool value) {} |
(...skipping 525 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
616 int sliceEnd = position + 20; | 621 int sliceEnd = position + 20; |
617 if (sliceEnd > source.length) { | 622 if (sliceEnd > source.length) { |
618 slice = "'${source.substring(position)}'"; | 623 slice = "'${source.substring(position)}'"; |
619 } else { | 624 } else { |
620 slice = "'${source.substring(position, sliceEnd)}...'"; | 625 slice = "'${source.substring(position, sliceEnd)}...'"; |
621 } | 626 } |
622 throw new FormatException("Unexpected character at $position: $slice"); | 627 throw new FormatException("Unexpected character at $position: $slice"); |
623 } | 628 } |
624 } | 629 } |
625 | 630 |
631 Object _defaultConvert(object) => object.toJson(); | |
626 | 632 |
627 class _JsonStringifier { | 633 class _JsonStringifier { |
628 StringSink sink; | 634 final Function converter; |
629 List<Object> seen; // TODO: that should be identity set. | 635 final StringSink sink; |
636 final Set<Object> seen; | |
630 | 637 |
631 _JsonStringifier(this.sink) : seen = []; | 638 _JsonStringifier(this.sink, this.converter) |
639 : this.seen = new HashSet.identity(); | |
632 | 640 |
633 static String stringify(final object) { | 641 static String stringify(final object, convert(object)) { |
634 StringBuffer output = new StringBuffer(); | 642 StringBuffer output = new StringBuffer(); |
635 _JsonStringifier stringifier = new _JsonStringifier(output); | 643 _JsonStringifier stringifier = new _JsonStringifier(output, convert); |
636 stringifier.stringifyValue(object); | 644 stringifier.stringifyValue(object); |
637 return output.toString(); | 645 return output.toString(); |
638 } | 646 } |
639 | 647 |
640 static void printOn(final object, StringSink output) { | 648 static void printOn(final object, StringSink output, convert(object)) { |
641 _JsonStringifier stringifier = new _JsonStringifier(output); | 649 _JsonStringifier stringifier = new _JsonStringifier(output, convert); |
642 stringifier.stringifyValue(object); | 650 stringifier.stringifyValue(object); |
643 } | 651 } |
644 | 652 |
645 static String numberToString(num x) { | 653 static String numberToString(num x) { |
646 return x.toString(); | 654 return x.toString(); |
647 } | 655 } |
648 | 656 |
649 // ('0' + x) or ('a' + x - 10) | 657 // ('0' + x) or ('a' + x - 10) |
650 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; | 658 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; |
651 | 659 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
688 charCodes.add(JsonParser.BACKSLASH); | 696 charCodes.add(JsonParser.BACKSLASH); |
689 charCodes.add(charCode); | 697 charCodes.add(charCode); |
690 } else { | 698 } else { |
691 charCodes.add(charCode); | 699 charCodes.add(charCode); |
692 } | 700 } |
693 } | 701 } |
694 sb.write(needsEscape ? new String.fromCharCodes(charCodes) : s); | 702 sb.write(needsEscape ? new String.fromCharCodes(charCodes) : s); |
695 } | 703 } |
696 | 704 |
697 void checkCycle(final object) { | 705 void checkCycle(final object) { |
698 // TODO: use Iterables. | 706 if (seen.contains(object)) { |
699 for (int i = 0; i < seen.length; i++) { | 707 throw new JsonCyclicError(object); |
700 if (identical(seen[i], object)) { | |
701 throw new JsonCyclicError(object); | |
702 } | |
703 } | 708 } |
704 seen.add(object); | 709 seen.add(object); |
705 } | 710 } |
706 | 711 |
707 void stringifyValue(final object) { | 712 void stringifyValue(final object) { |
708 // Tries stringifying object directly. If it's not a simple value, List or | 713 // Tries stringifying object directly. If it's not a simple value, List or |
709 // Map, call toJson() to get a custom representation and try serializing | 714 // Map, call toJson() to get a custom representation and try serializing |
710 // that. | 715 // that. |
711 if (!stringifyJsonValue(object)) { | 716 if (!stringifyJsonValue(object)) { |
712 checkCycle(object); | 717 checkCycle(object); |
713 try { | 718 try { |
714 var customJson = object.toJson(); | 719 var customJson = converter(object); |
715 if (!stringifyJsonValue(customJson)) { | 720 if (!stringifyJsonValue(customJson)) { |
716 throw new JsonUnsupportedObjectError(object); | 721 throw new JsonUnsupportedObjectError(object); |
717 } | 722 } |
718 seen.removeLast(); | 723 seen.remove(object); |
719 } catch (e) { | 724 } catch (e) { |
720 throw new JsonUnsupportedObjectError(object, cause: e); | 725 throw new JsonUnsupportedObjectError(object, cause: e); |
721 } | 726 } |
722 } | 727 } |
723 } | 728 } |
724 | 729 |
725 /** | 730 /** |
726 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. | 731 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. |
727 * | 732 * |
728 * Returns true if the value is one of these types, and false if not. | 733 * Returns true if the value is one of these types, and false if not. |
(...skipping 24 matching lines...) Expand all Loading... | |
753 sink.write('['); | 758 sink.write('['); |
754 if (a.length > 0) { | 759 if (a.length > 0) { |
755 stringifyValue(a[0]); | 760 stringifyValue(a[0]); |
756 // TODO: switch to Iterables. | 761 // TODO: switch to Iterables. |
757 for (int i = 1; i < a.length; i++) { | 762 for (int i = 1; i < a.length; i++) { |
758 sink.write(','); | 763 sink.write(','); |
759 stringifyValue(a[i]); | 764 stringifyValue(a[i]); |
760 } | 765 } |
761 } | 766 } |
762 sink.write(']'); | 767 sink.write(']'); |
763 seen.removeLast(); | 768 seen.remove(object); |
764 return true; | 769 return true; |
765 } else if (object is Map) { | 770 } else if (object is Map) { |
766 checkCycle(object); | 771 checkCycle(object); |
767 Map<String, Object> m = object; | 772 Map<String, Object> m = object; |
768 sink.write('{'); | 773 sink.write('{'); |
769 bool first = true; | 774 bool first = true; |
770 m.forEach((String key, Object value) { | 775 m.forEach((String key, Object value) { |
771 if (!first) { | 776 if (!first) { |
772 sink.write(',"'); | 777 sink.write(',"'); |
773 } else { | 778 } else { |
774 sink.write('"'); | 779 sink.write('"'); |
775 } | 780 } |
776 escape(sink, key); | 781 escape(sink, key); |
777 sink.write('":'); | 782 sink.write('":'); |
778 stringifyValue(value); | 783 stringifyValue(value); |
779 first = false; | 784 first = false; |
780 }); | 785 }); |
781 sink.write('}'); | 786 sink.write('}'); |
782 seen.removeLast(); | 787 seen.remove(object); |
783 return true; | 788 return true; |
784 } else { | 789 } else { |
785 return false; | 790 return false; |
786 } | 791 } |
787 } | 792 } |
788 } | 793 } |
OLD | NEW |