| // Copyright (c) 2012, 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. |
| |
| /** |
| * Simple library to serialize acyclic Dart types to JSON. |
| * This library is not intended for broad consumption and should be replaced |
| * with a more generic Dart serialization library when one is available. |
| */ |
| library json_serializer; |
| |
| import 'dart:async'; |
| import 'dart:mirrors'; |
| import 'dart:json' as json; |
| |
| String serialize(Object o) { |
| var printer = new JsonPrinter(); |
| _serialize(null, o, printer); |
| return printer.toString(); |
| } |
| |
| /// Serialize the object with pretty printing. |
| String prettySerialize(Object o) { |
| var printer = new JsonPrinter(_prettyPrint: true); |
| _serialize(null, o, printer); |
| return printer.toString(); |
| } |
| |
| |
| void _serialize(String name, Object o, JsonPrinter printer) { |
| if (o == null) return; |
| |
| if (o is List) { |
| _serializeList(name, o, printer); |
| } else if (o is Map) { |
| _serializeMap(name, o, printer); |
| } else if (o is String) { |
| printer.addString(name, o); |
| } else if (o is bool) { |
| printer.addBool(name, o); |
| } else { |
| _serializeObject(name, o, printer); |
| } |
| } |
| |
| void _serializeObject(String name, Object o, JsonPrinter printer) { |
| printer.startObject(name); |
| |
| var mirror = reflect(o); |
| var classMirror = mirror.type; |
| var members = <String>[]; |
| determineAllMembers(classMirror, members); |
| |
| // TODO(jacobr): this code works only because futures for mirrors return |
| // immediately. |
| for(String memberName in members) { |
| var result = deprecatedFutureValue(mirror.getFieldAsync(memberName)); |
| _serialize(memberName, result.reflectee, printer); |
| } |
| printer.endObject(); |
| } |
| |
| void determineAllMembers(ClassMirror classMirror, |
| List<String> members) { |
| for(String getterName in classMirror.getters.keys) { |
| if (!members.contains(getterName)) { |
| members.add(getterName); |
| } |
| } |
| for(String fieldName in classMirror.variables.keys) { |
| if (!members.contains(fieldName)) { |
| members.add(fieldName); |
| } |
| } |
| if (classMirror.superclass != null && |
| classMirror.superclass.qualifiedName != classMirror.qualifiedName && |
| classMirror.superclass.qualifiedName != 'dart.core.Object') { |
| determineAllMembers(classMirror.superclass, members); |
| } |
| } |
| |
| void _serializeList(String name, List l, JsonPrinter printer) { |
| printer.startList(name); |
| for(var o in l) { |
| _serialize(null, o, printer); |
| } |
| printer.endList(); |
| } |
| |
| void _serializeMap(String name, Map m, JsonPrinter printer) { |
| printer.startObject(name); |
| m.forEach((key, value) => |
| _serialize(key, value, printer)); |
| printer.endObject(); |
| } |
| |
| class JsonPrinter { |
| static const int BACKSPACE = 8; |
| static const int TAB = 9; |
| static const int NEW_LINE = 10; |
| static const int FORM_FEED = 12; |
| static const int CARRIAGE_RETURN = 13; |
| static const int QUOTE = 34; |
| static const int BACKSLASH = 92; |
| static const int CHAR_B = 98; |
| static const int CHAR_F = 102; |
| static const int CHAR_N = 110; |
| static const int CHAR_R = 114; |
| static const int CHAR_T = 116; |
| static const int CHAR_U = 117; |
| |
| StringBuffer _sb; |
| int _indent = 0; |
| bool _inSet = false; |
| |
| bool _prettyPrint; |
| JsonPrinter({this._prettyPrint: false}) { |
| _sb = new StringBuffer(); |
| } |
| |
| void startObject(String name) { |
| _start(name); |
| _sb.write('{'); |
| |
| _indent += 1; |
| _inSet = false; |
| } |
| |
| void endObject() { |
| _indent -= 1; |
| if (_inSet) { |
| _newline(); |
| } |
| _sb.write('}'); |
| _inSet = true; |
| } |
| |
| void startList(String name) { |
| _start(name); |
| _inSet = false; |
| |
| _sb.write('['); |
| _indent += 1; |
| } |
| |
| void endList() { |
| _indent -= 1; |
| if (_inSet) { |
| _newline(); |
| } |
| _sb.write(']'); |
| _inSet = true; |
| } |
| |
| void addString(String name, String value) { |
| _start(name); |
| _sb.write('"'); |
| _escape(_sb, value); |
| _sb.write('"'); |
| _inSet = true; |
| } |
| |
| void addBool(String name, bool value) { |
| _start(name); |
| _sb.write(value.toString()); |
| _inSet = true; |
| } |
| |
| void addNum(String name, num value) { |
| _start(name); |
| _sb.write(value.toString()); |
| _inSet = true; |
| } |
| |
| void _start(String name) { |
| if (_inSet) { |
| _sb.write(','); |
| } |
| // Do not print a newline at the beginning of the file. |
| if (!_sb.isEmpty) { |
| _newline(); |
| } |
| if (name != null) { |
| _sb.write('"$name": '); |
| } |
| } |
| |
| void _newline([int indent = 0]) { |
| _sb.write('\n'); |
| _indent += indent; |
| |
| for (var i = 0; i < _indent; ++i) { |
| _sb.write(' '); |
| } |
| } |
| |
| String toString() { |
| if (_prettyPrint) { |
| return _sb.toString(); |
| } else { |
| // Convenient hack to remove the pretty printing this serializer adds by |
| // default. |
| return json.stringify(json.parse(_sb.toString())); |
| } |
| } |
| |
| static int _hexDigit(int x) => x < 10 ? 48 + x : 87 + x; |
| |
| static void _escape(StringBuffer sb, String s) { |
| final int length = s.length; |
| bool needsEscape = false; |
| final codeUnits = new List<int>(); |
| for (int i = 0; i < length; i++) { |
| int codeUnit = s.codeUnitAt(i); |
| if (codeUnit < 32) { |
| needsEscape = true; |
| codeUnits.add(JsonPrinter.BACKSLASH); |
| switch (codeUnit) { |
| case JsonPrinter.BACKSPACE: |
| codeUnits.add(JsonPrinter.CHAR_B); |
| break; |
| case JsonPrinter.TAB: |
| codeUnits.add(JsonPrinter.CHAR_T); |
| break; |
| case JsonPrinter.NEW_LINE: |
| codeUnits.add(JsonPrinter.CHAR_N); |
| break; |
| case JsonPrinter.FORM_FEED: |
| codeUnits.add(JsonPrinter.CHAR_F); |
| break; |
| case JsonPrinter.CARRIAGE_RETURN: |
| codeUnits.add(JsonPrinter.CHAR_R); |
| break; |
| default: |
| codeUnits.add(JsonPrinter.CHAR_U); |
| codeUnits.add(_hexDigit((codeUnit >> 12) & 0xf)); |
| codeUnits.add(_hexDigit((codeUnit >> 8) & 0xf)); |
| codeUnits.add(_hexDigit((codeUnit >> 4) & 0xf)); |
| codeUnits.add(_hexDigit(codeUnit & 0xf)); |
| break; |
| } |
| } else if (codeUnit == JsonPrinter.QUOTE || |
| codeUnit == JsonPrinter.BACKSLASH) { |
| needsEscape = true; |
| codeUnits.add(JsonPrinter.BACKSLASH); |
| codeUnits.add(codeUnit); |
| } else { |
| codeUnits.add(codeUnit); |
| } |
| } |
| sb.write(needsEscape ? new String.fromCharCodes(codeUnits) : s); |
| } |
| } |