| 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 json; | 9 library json; |
| 10 | 10 |
| 11 import "dart:collection" show HashSet; |
| 12 |
| 11 // JSON parsing and serialization. | 13 // JSON parsing and serialization. |
| 12 | 14 |
| 13 /** | 15 /** |
| 14 * Error thrown by JSON serialization if an object cannot be serialized. | 16 * Error thrown by JSON serialization if an object cannot be serialized. |
| 15 * | 17 * |
| 16 * The [unsupportedObject] field holds that object that failed to be serialized. | 18 * The [unsupportedObject] field holds that object that failed to be serialized. |
| 17 * | 19 * |
| 18 * If an object isn't directly serializable, the serializer calls the 'toJson' | 20 * If an object isn't directly serializable, the serializer calls the 'toJson' |
| 19 * method on the object. If that call fails, the error will be stored in the | 21 * method on the object. If that call fails, the error will be stored in the |
| 20 * [cause] field. If the call returns an object that isn't directly | 22 * [cause] field. If the call returns an object that isn't directly |
| (...skipping 637 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 658 slice = "'${source.substring(position)}'"; | 660 slice = "'${source.substring(position)}'"; |
| 659 } else { | 661 } else { |
| 660 slice = "'${source.substring(position, sliceEnd)}...'"; | 662 slice = "'${source.substring(position, sliceEnd)}...'"; |
| 661 } | 663 } |
| 662 throw new FormatException("Unexpected character at $position: $slice"); | 664 throw new FormatException("Unexpected character at $position: $slice"); |
| 663 } | 665 } |
| 664 } | 666 } |
| 665 | 667 |
| 666 | 668 |
| 667 class _JsonStringifier { | 669 class _JsonStringifier { |
| 668 StringSink sink; | 670 final StringSink sink; |
| 669 List<Object> seen; // TODO: that should be identity set. | 671 final Set<Object> seen; |
| 670 | 672 |
| 671 _JsonStringifier(this.sink) : seen = []; | 673 _JsonStringifier(this.sink) : seen = new HashSet.identity(); |
| 672 | 674 |
| 673 static String stringify(final object) { | 675 static String stringify(final object) { |
| 674 StringBuffer output = new StringBuffer(); | 676 StringBuffer output = new StringBuffer(); |
| 675 _JsonStringifier stringifier = new _JsonStringifier(output); | 677 _JsonStringifier stringifier = new _JsonStringifier(output); |
| 676 stringifier.stringifyValue(object); | 678 stringifier.stringifyValue(object); |
| 677 return output.toString(); | 679 return output.toString(); |
| 678 } | 680 } |
| 679 | 681 |
| 680 static void printOn(final object, StringSink output) { | 682 static void printOn(final object, StringSink output) { |
| 681 _JsonStringifier stringifier = new _JsonStringifier(output); | 683 _JsonStringifier stringifier = new _JsonStringifier(output); |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 728 charCodes.add(JsonParser.BACKSLASH); | 730 charCodes.add(JsonParser.BACKSLASH); |
| 729 charCodes.add(charCode); | 731 charCodes.add(charCode); |
| 730 } else { | 732 } else { |
| 731 charCodes.add(charCode); | 733 charCodes.add(charCode); |
| 732 } | 734 } |
| 733 } | 735 } |
| 734 sb.write(needsEscape ? new String.fromCharCodes(charCodes) : s); | 736 sb.write(needsEscape ? new String.fromCharCodes(charCodes) : s); |
| 735 } | 737 } |
| 736 | 738 |
| 737 void checkCycle(final object) { | 739 void checkCycle(final object) { |
| 738 // TODO: use Iterables. | 740 if (seen.contains(object)) { |
| 739 for (int i = 0; i < seen.length; i++) { | 741 throw new JsonCyclicError(object); |
| 740 if (identical(seen[i], object)) { | |
| 741 throw new JsonCyclicError(object); | |
| 742 } | |
| 743 } | 742 } |
| 744 seen.add(object); | 743 seen.add(object); |
| 745 } | 744 } |
| 746 | 745 |
| 747 void stringifyValue(final object) { | 746 void stringifyValue(final object) { |
| 748 // Tries stringifying object directly. If it's not a simple value, List or | 747 // Tries stringifying object directly. If it's not a simple value, List or |
| 749 // Map, call toJson() to get a custom representation and try serializing | 748 // Map, call toJson() to get a custom representation and try serializing |
| 750 // that. | 749 // that. |
| 751 if (!stringifyJsonValue(object)) { | 750 if (!stringifyJsonValue(object)) { |
| 752 checkCycle(object); | 751 checkCycle(object); |
| 753 try { | 752 try { |
| 754 var customJson = object.toJson(); | 753 var customJson = object.toJson(); |
| 755 if (!stringifyJsonValue(customJson)) { | 754 if (!stringifyJsonValue(customJson)) { |
| 756 throw new JsonUnsupportedObjectError(object); | 755 throw new JsonUnsupportedObjectError(object); |
| 757 } | 756 } |
| 758 seen.removeLast(); | 757 seen.remove(object); |
| 759 } catch (e) { | 758 } catch (e) { |
| 760 throw new JsonUnsupportedObjectError(object, cause: e); | 759 throw new JsonUnsupportedObjectError(object, cause: e); |
| 761 } | 760 } |
| 762 } | 761 } |
| 763 } | 762 } |
| 764 | 763 |
| 765 /** | 764 /** |
| 766 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. | 765 * Serializes a [num], [String], [bool], [Null], [List] or [Map] value. |
| 767 * | 766 * |
| 768 * Returns true if the value is one of these types, and false if not. | 767 * Returns true if the value is one of these types, and false if not. |
| (...skipping 24 matching lines...) Expand all Loading... |
| 793 sink.write('['); | 792 sink.write('['); |
| 794 if (a.length > 0) { | 793 if (a.length > 0) { |
| 795 stringifyValue(a[0]); | 794 stringifyValue(a[0]); |
| 796 // TODO: switch to Iterables. | 795 // TODO: switch to Iterables. |
| 797 for (int i = 1; i < a.length; i++) { | 796 for (int i = 1; i < a.length; i++) { |
| 798 sink.write(','); | 797 sink.write(','); |
| 799 stringifyValue(a[i]); | 798 stringifyValue(a[i]); |
| 800 } | 799 } |
| 801 } | 800 } |
| 802 sink.write(']'); | 801 sink.write(']'); |
| 803 seen.removeLast(); | 802 seen.remove(object); |
| 804 return true; | 803 return true; |
| 805 } else if (object is Map) { | 804 } else if (object is Map) { |
| 806 checkCycle(object); | 805 checkCycle(object); |
| 807 Map<String, Object> m = object; | 806 Map<String, Object> m = object; |
| 808 sink.write('{'); | 807 sink.write('{'); |
| 809 bool first = true; | 808 bool first = true; |
| 810 m.forEach((String key, Object value) { | 809 m.forEach((String key, Object value) { |
| 811 if (!first) { | 810 if (!first) { |
| 812 sink.write(',"'); | 811 sink.write(',"'); |
| 813 } else { | 812 } else { |
| 814 sink.write('"'); | 813 sink.write('"'); |
| 815 } | 814 } |
| 816 escape(sink, key); | 815 escape(sink, key); |
| 817 sink.write('":'); | 816 sink.write('":'); |
| 818 stringifyValue(value); | 817 stringifyValue(value); |
| 819 first = false; | 818 first = false; |
| 820 }); | 819 }); |
| 821 sink.write('}'); | 820 sink.write('}'); |
| 822 seen.removeLast(); | 821 seen.remove(object); |
| 823 return true; | 822 return true; |
| 824 } else { | 823 } else { |
| 825 return false; | 824 return false; |
| 826 } | 825 } |
| 827 } | 826 } |
| 828 } | 827 } |
| OLD | NEW |