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 |