Chromium Code Reviews| 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 |