| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 /** | |
| 6 * | |
| 7 * Encoders and decoders for converting between different data representations, | |
| 8 * including JSON and UTF-8. | |
| 9 * | |
| 10 * In addition to converters for common data representations, this library | |
| 11 * provides support for implementing converters in a way which makes them easy t
o | |
| 12 * chain and to use with streams. | |
| 13 * | |
| 14 * The `dart:convert` library works in both web apps and command-line apps. | |
| 15 * To use it: | |
| 16 * | |
| 17 * import 'dart:convert'; | |
| 18 * | |
| 19 * Two commonly used converters are the top-level instances of | |
| 20 * [JsonCodec] and [Utf8Codec], named JSON and UTF8, respectively. | |
| 21 * | |
| 22 * JSON is a simple text format for representing | |
| 23 * structured objects and collections. | |
| 24 * The JSON encoder/decoder transforms between strings and | |
| 25 * object structures, such as lists and maps, using the JSON format. | |
| 26 * | |
| 27 * UTF-8 is a common variable-width encoding that can represent | |
| 28 * every character in the Unicode character set. | |
| 29 * The UTF-8 encoder/decoder transforms between Strings and bytes. | |
| 30 * | |
| 31 * Converters are often used with streams | |
| 32 * to transform the data that comes through the stream | |
| 33 * as it becomes available. | |
| 34 * The following code uses two converters. | |
| 35 * The first is a UTF-8 decoder, which converts the data from bytes to UTF-8 | |
| 36 * as it's read from a file, | |
| 37 * The second is an instance of [LineSplitter], | |
| 38 * which splits the data on newline boundaries. | |
| 39 * | |
| 40 * int lineNumber = 1; | |
| 41 * Stream<List<int>> stream = new File('quotes.txt').openRead(); | |
| 42 * | |
| 43 * stream.transform(UTF8.decoder) | |
| 44 * .transform(const LineSplitter()) | |
| 45 * .listen((line) { | |
| 46 * if (showLineNumbers) { | |
| 47 * stdout.write('${lineNumber++} '); | |
| 48 * } | |
| 49 * stdout.writeln(line); | |
| 50 * }); | |
| 51 * | |
| 52 * See the documentation for the [Codec] and [Converter] classes | |
| 53 * for information about creating your own converters. | |
| 54 */ | |
| 55 library dart.convert; | |
| 56 | |
| 57 import 'dart:async'; | |
| 58 import 'dart:typed_data'; | |
| 59 import 'dart:_js_helper' show patch; | |
| 60 import 'dart:_foreign_helper' show JS; | |
| 61 import 'dart:_interceptors' show JSExtendableArray; | |
| 62 import 'dart:_internal' show MappedIterable, ListIterable; | |
| 63 import 'dart:collection' show Maps, LinkedHashMap; | |
| 64 | |
| 65 part 'ascii.dart'; | |
| 66 part 'byte_conversion.dart'; | |
| 67 part 'chunked_conversion.dart'; | |
| 68 part 'codec.dart'; | |
| 69 part 'converter.dart'; | |
| 70 part 'encoding.dart'; | |
| 71 part 'html_escape.dart'; | |
| 72 part 'json.dart'; | |
| 73 part 'latin1.dart'; | |
| 74 part 'line_splitter.dart'; | |
| 75 part 'string_conversion.dart'; | |
| 76 part 'utf.dart'; | |
| 77 | |
| 78 /** | |
| 79 * Walks the raw JavaScript value [json], replacing JavaScript Objects with | |
| 80 * Maps. [json] is expected to be freshly allocated so elements can be replaced | |
| 81 * in-place. | |
| 82 */ | |
| 83 _convertJsonToDart(json, reviver(key, value)) { | |
| 84 assert(reviver != null); | |
| 85 walk(e) { | |
| 86 // JavaScript null, string, number, bool are in the correct representation. | |
| 87 if (JS('bool', '# == null', e) || JS('bool', 'typeof # != "object"', e)) { | |
| 88 return e; | |
| 89 } | |
| 90 | |
| 91 // This test is needed to avoid identifing '{"__proto__":[]}' as an Array. | |
| 92 // TODO(sra): Replace this test with cheaper '#.constructor === Array' when | |
| 93 // bug 621 below is fixed. | |
| 94 if (JS('bool', 'Object.getPrototypeOf(#) === Array.prototype', e)) { | |
| 95 // In-place update of the elements since JS Array is a Dart List. | |
| 96 for (int i = 0; i < JS('int', '#.length', e); i++) { | |
| 97 // Use JS indexing to avoid range checks. We know this is the only | |
| 98 // reference to the list, but the compiler will likely never be able to | |
| 99 // tell that this instance of the list cannot have its length changed by | |
| 100 // the reviver even though it later will be passed to the reviver at the | |
| 101 // outer level. | |
| 102 var item = JS('', '#[#]', e, i); | |
| 103 JS('', '#[#]=#', e, i, reviver(i, walk(item))); | |
| 104 } | |
| 105 return e; | |
| 106 } | |
| 107 | |
| 108 // Otherwise it is a plain object, so copy to a JSON map, so we process | |
| 109 // and revive all entries recursively. | |
| 110 _JsonMap map = new _JsonMap(e); | |
| 111 var processed = map._processed; | |
| 112 List<String> keys = map._computeKeys(); | |
| 113 for (int i = 0; i < keys.length; i++) { | |
| 114 String key = keys[i]; | |
| 115 var revived = reviver(key, walk(JS('', '#[#]', e, key))); | |
| 116 JS('', '#[#]=#', processed, key, revived); | |
| 117 } | |
| 118 | |
| 119 // Update the JSON map structure so future access is cheaper. | |
| 120 map._original = processed; // Don't keep two objects around. | |
| 121 return map; | |
| 122 } | |
| 123 | |
| 124 return reviver(null, walk(json)); | |
| 125 } | |
| 126 _convertJsonToDartLazy(object) { | |
| 127 // JavaScript null and undefined are represented as null. | |
| 128 if (object == null) return null; | |
| 129 | |
| 130 // JavaScript string, number, bool already has the correct representation. | |
| 131 if (JS('bool', 'typeof # != "object"', object)) { | |
| 132 return object; | |
| 133 } | |
| 134 | |
| 135 // This test is needed to avoid identifing '{"__proto__":[]}' as an array. | |
| 136 // TODO(sra): Replace this test with cheaper '#.constructor === Array' when | |
| 137 // bug https://code.google.com/p/v8/issues/detail?id=621 is fixed. | |
| 138 if (JS('bool', 'Object.getPrototypeOf(#) !== Array.prototype', object)) { | |
| 139 return new _JsonMap(object); | |
| 140 } | |
| 141 | |
| 142 // Update the elements in place since JS arrays are Dart lists. | |
| 143 for (int i = 0; i < JS('int', '#.length', object); i++) { | |
| 144 // Use JS indexing to avoid range checks. We know this is the only | |
| 145 // reference to the list, but the compiler will likely never be able to | |
| 146 // tell that this instance of the list cannot have its length changed by | |
| 147 // the reviver even though it later will be passed to the reviver at the | |
| 148 // outer level. | |
| 149 var item = JS('', '#[#]', object, i); | |
| 150 JS('', '#[#]=#', object, i, _convertJsonToDartLazy(item)); | |
| 151 } | |
| 152 return object; | |
| 153 } | |
| 154 class _JsonMap implements LinkedHashMap { | |
| 155 // The original JavaScript object remains unchanged until | |
| 156 // the map is eventually upgraded, in which case we null it | |
| 157 // out to reclaim the memory used by it. | |
| 158 var _original; | |
| 159 | |
| 160 // We keep track of the map entries that we have already | |
| 161 // processed by adding them to a separate JavaScript object. | |
| 162 var _processed = _newJavaScriptObject(); | |
| 163 | |
| 164 // If the data slot isn't null, it represents either the list | |
| 165 // of keys (for non-upgraded JSON maps) or the upgraded map. | |
| 166 var _data = null; | |
| 167 | |
| 168 _JsonMap(this._original); | |
| 169 | |
| 170 operator[](Object key) { | |
| 171 if (_isUpgraded) { | |
| 172 return _upgradedMap[key]; | |
| 173 } else if (key is !String) { | |
| 174 return null; | |
| 175 } else { | |
| 176 var result = _getProperty(_processed, key); | |
| 177 if (_isUnprocessed(result)) result = _process(key); | |
| 178 return result; | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 int get length => _isUpgraded | |
| 183 ? _upgradedMap.length | |
| 184 : _computeKeys().length; | |
| 185 | |
| 186 bool get isEmpty => length == 0; | |
| 187 bool get isNotEmpty => length > 0; | |
| 188 | |
| 189 Iterable get keys { | |
| 190 if (_isUpgraded) return _upgradedMap.keys; | |
| 191 return new _JsonMapKeyIterable(this); | |
| 192 } | |
| 193 | |
| 194 Iterable get values { | |
| 195 if (_isUpgraded) return _upgradedMap.values; | |
| 196 return new MappedIterable(_computeKeys(), (each) => this[each]); | |
| 197 } | |
| 198 | |
| 199 operator[]=(key, value) { | |
| 200 if (_isUpgraded) { | |
| 201 _upgradedMap[key] = value; | |
| 202 } else if (containsKey(key)) { | |
| 203 var processed = _processed; | |
| 204 _setProperty(processed, key, value); | |
| 205 var original = _original; | |
| 206 if (!identical(original, processed)) { | |
| 207 _setProperty(original, key, null); // Reclaim memory. | |
| 208 } | |
| 209 } else { | |
| 210 _upgrade()[key] = value; | |
| 211 } | |
| 212 } | |
| 213 | |
| 214 void addAll(Map other) { | |
| 215 other.forEach((key, value) { | |
| 216 this[key] = value; | |
| 217 }); | |
| 218 } | |
| 219 | |
| 220 bool containsValue(Object value) { | |
| 221 if (_isUpgraded) return _upgradedMap.containsValue(value); | |
| 222 List<String> keys = _computeKeys(); | |
| 223 for (int i = 0; i < keys.length; i++) { | |
| 224 String key = keys[i]; | |
| 225 if (this[key] == value) return true; | |
| 226 } | |
| 227 return false; | |
| 228 } | |
| 229 | |
| 230 bool containsKey(Object key) { | |
| 231 if (_isUpgraded) return _upgradedMap.containsKey(key); | |
| 232 if (key is !String) return false; | |
| 233 return _hasProperty(_original, key); | |
| 234 } | |
| 235 | |
| 236 putIfAbsent(key, ifAbsent()) { | |
| 237 if (containsKey(key)) return this[key]; | |
| 238 var value = ifAbsent(); | |
| 239 this[key] = value; | |
| 240 return value; | |
| 241 } | |
| 242 | |
| 243 remove(Object key) { | |
| 244 if (!_isUpgraded && !containsKey(key)) return null; | |
| 245 return _upgrade().remove(key); | |
| 246 } | |
| 247 | |
| 248 void clear() { | |
| 249 if (_isUpgraded) { | |
| 250 _upgradedMap.clear(); | |
| 251 } else { | |
| 252 if (_data != null) { | |
| 253 // Clear the list of keys to make sure we force | |
| 254 // a concurrent modification error if anyone is | |
| 255 // currently iterating over it. | |
| 256 _data.clear(); | |
| 257 } | |
| 258 _original = _processed = null; | |
| 259 _data = {}; | |
| 260 } | |
| 261 } | |
| 262 | |
| 263 void forEach(void f(key, value)) { | |
| 264 if (_isUpgraded) return _upgradedMap.forEach(f); | |
| 265 List<String> keys = _computeKeys(); | |
| 266 for (int i = 0; i < keys.length; i++) { | |
| 267 String key = keys[i]; | |
| 268 | |
| 269 // Compute the value under the assumption that the property | |
| 270 // is present but potentially not processed. | |
| 271 var value = _getProperty(_processed, key); | |
| 272 if (_isUnprocessed(value)) { | |
| 273 value = _convertJsonToDartLazy(_getProperty(_original, key)); | |
| 274 _setProperty(_processed, key, value); | |
| 275 } | |
| 276 | |
| 277 // Do the callback. | |
| 278 f(key, value); | |
| 279 | |
| 280 // Check if invoking the callback function changed | |
| 281 // the key set. If so, throw an exception. | |
| 282 if (!identical(keys, _data)) { | |
| 283 throw new ConcurrentModificationError(this); | |
| 284 } | |
| 285 } | |
| 286 } | |
| 287 | |
| 288 String toString() => Maps.mapToString(this); | |
| 289 | |
| 290 | |
| 291 // ------------------------------------------ | |
| 292 // Private helper methods. | |
| 293 // ------------------------------------------ | |
| 294 | |
| 295 bool get _isUpgraded => _processed == null; | |
| 296 | |
| 297 Map get _upgradedMap { | |
| 298 assert(_isUpgraded); | |
| 299 // 'cast' the union type to LinkedHashMap. It would be even better if we | |
| 300 // could 'cast' to the implementation type, since LinkedHashMap includes | |
| 301 // _JsonMap. | |
| 302 return JS('LinkedHashMap', '#', _data); | |
| 303 } | |
| 304 | |
| 305 List<String> _computeKeys() { | |
| 306 assert(!_isUpgraded); | |
| 307 List keys = _data; | |
| 308 if (keys == null) { | |
| 309 keys = _data = _getPropertyNames(_original); | |
| 310 } | |
| 311 return JS('JSExtendableArray', '#', keys); | |
| 312 } | |
| 313 | |
| 314 Map _upgrade() { | |
| 315 if (_isUpgraded) return _upgradedMap; | |
| 316 | |
| 317 // Copy all the (key, value) pairs to a freshly allocated | |
| 318 // linked hash map thus preserving the ordering. | |
| 319 Map result = {}; | |
| 320 List<String> keys = _computeKeys(); | |
| 321 for (int i = 0; i < keys.length; i++) { | |
| 322 String key = keys[i]; | |
| 323 result[key] = this[key]; | |
| 324 } | |
| 325 | |
| 326 // We only upgrade when we need to extend the map, so we can | |
| 327 // safely force a concurrent modification error in case | |
| 328 // someone is iterating over the map here. | |
| 329 if (keys.isEmpty) { | |
| 330 keys.add(null); | |
| 331 } else { | |
| 332 keys.clear(); | |
| 333 } | |
| 334 | |
| 335 // Clear out the associated JavaScript objects and mark the | |
| 336 // map as having been upgraded. | |
| 337 _original = _processed = null; | |
| 338 _data = result; | |
| 339 assert(_isUpgraded); | |
| 340 return result; | |
| 341 } | |
| 342 | |
| 343 _process(String key) { | |
| 344 if (!_hasProperty(_original, key)) return null; | |
| 345 var result = _convertJsonToDartLazy(_getProperty(_original, key)); | |
| 346 return _setProperty(_processed, key, result); | |
| 347 } | |
| 348 | |
| 349 | |
| 350 // ------------------------------------------ | |
| 351 // Private JavaScript helper methods. | |
| 352 // ------------------------------------------ | |
| 353 | |
| 354 static bool _hasProperty(object, String key) | |
| 355 => JS('bool', 'Object.prototype.hasOwnProperty.call(#,#)', object, key); | |
| 356 static _getProperty(object, String key) | |
| 357 => JS('', '#[#]', object, key); | |
| 358 static _setProperty(object, String key, value) | |
| 359 => JS('', '#[#]=#', object, key, value); | |
| 360 static List _getPropertyNames(object) | |
| 361 => JS('JSExtendableArray', 'Object.keys(#)', object); | |
| 362 static bool _isUnprocessed(object) | |
| 363 => JS('bool', 'typeof(#)=="undefined"', object); | |
| 364 static _newJavaScriptObject() | |
| 365 => JS('=Object', 'Object.create(null)'); | |
| 366 } | |
| 367 class _JsonMapKeyIterable extends ListIterable { | |
| 368 final _JsonMap _parent; | |
| 369 | |
| 370 _JsonMapKeyIterable(this._parent); | |
| 371 | |
| 372 int get length => _parent.length; | |
| 373 | |
| 374 String elementAt(int index) { | |
| 375 return _parent._isUpgraded ? _parent.keys.elementAt(index) | |
| 376 : _parent._computeKeys()[index]; | |
| 377 } | |
| 378 | |
| 379 /// Although [ListIterable] defines its own iterator, we return the iterator | |
| 380 /// of the underlying list [_keys] in order to propagate | |
| 381 /// [ConcurrentModificationError]s. | |
| 382 Iterator get iterator { | |
| 383 return _parent._isUpgraded ? _parent.keys.iterator | |
| 384 : _parent._computeKeys().iterator; | |
| 385 } | |
| 386 | |
| 387 /// Delegate to [parent.containsKey] to ensure the performance expected | |
| 388 /// from [Map.keys.containsKey]. | |
| 389 bool contains(Object key) => _parent.containsKey(key); | |
| 390 } | |
| 391 /** | |
| 392 * Implements the chunked conversion from a JSON string to its corresponding | |
| 393 * object. | |
| 394 * | |
| 395 * The sink only creates one object, but its input can be chunked. | |
| 396 */ | |
| 397 // TODO(floitsch): don't accumulate everything before starting to decode. | |
| 398 class _JsonDecoderSink extends _StringSinkConversionSink { | |
| 399 final _Reviver _reviver; | |
| 400 final Sink<Object> _sink; | |
| 401 | |
| 402 _JsonDecoderSink(this._reviver, this._sink) | |
| 403 : super(new StringBuffer()); | |
| 404 | |
| 405 void close() { | |
| 406 super.close(); | |
| 407 StringBuffer buffer = _stringSink; | |
| 408 String accumulated = buffer.toString(); | |
| 409 buffer.clear(); | |
| 410 Object decoded = _parseJson(accumulated, _reviver); | |
| 411 _sink.add(decoded); | |
| 412 _sink.close(); | |
| 413 } | |
| 414 } | |
| OLD | NEW |