blob: 01cce1a0acf915537b717b57237cf0a44e174cfb [file] [log] [blame]
// Copyright (c) 2013, 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.
// Patch file for dart:json library.
import 'dart:_foreign_helper' show JS;
/**
* Parses [json] and builds the corresponding parsed JSON value.
*
* Parsed JSON values are of the types [num], [String], [bool], [Null],
* [List]s of parsed JSON values or [Map]s from [String] to parsed
* JSON values.
*
* The optional [reviver] function, if provided, is called once for each object
* or list property parsed. The arguments are the property name ([String]) or
* list index ([int]), and the value is the parsed value. The return value of
* the reviver will be used as the value of that property instead of the parsed
* value. The top level value is passed to the reviver with the empty string as
* a key.
*
* Throws [FormatException] if the input is not valid JSON text.
*/
patch parse(String json, [reviver(var key, var value)]) {
if (json is! String) throw new ArgumentError(json);
var parsed;
try {
parsed = JS('=Object|=List|Null|bool|num|String', 'JSON.parse(#)', json);
} catch (e) {
throw new FormatException(JS('String', 'String(#)', e));
}
return _convertJsonToDart(parsed, reviver);
}
/**
* Walks the raw JavaScript value [json], replacing JavaScript Objects with
* Maps. [json] is expected to be freshly allocated so elements can be replaced
* in-place.
*/
_convertJsonToDart(json, reviver(key, value)) {
var revive = reviver == null ? (key, value) => value : reviver;
walk(e) {
// JavaScript null, string, number, bool are in the correct representation.
if (JS('bool', '# == null', e) || JS('bool', 'typeof # != "object"', e)) {
return e;
}
// This test is needed to avoid identifing '{"__proto__":[]}' as an Array.
// TODO(sra): Replace this test with cheaper '#.constructor === Array' when
// bug 621 below is fixed.
if (JS('bool', 'Object.getPrototypeOf(#) === Array.prototype', e)) {
var list = JS('=List', '#', e); // Teach compiler the type is known.
// In-place update of the elements since JS Array is a Dart List.
for (int i = 0; i < list.length; i++) {
// Use JS indexing to avoid range checks. We know this is the only
// reference to the list, but the compiler will likely never be able to
// tell that this instance of the list cannot have its length changed by
// the reviver even though it later will be passed to the reviver at the
// outer level.
var item = JS('', '#[#]', list, i);
JS('', '#[#]=#', list, i, revive(i, walk(item)));
}
return list;
}
// Otherwise it is a plain Object, so copy to a Map.
var keys = JS('=List', 'Object.keys(#)', e);
Map map = {};
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
map[key] = revive(key, walk(JS('', '#[#]', e, key)));
}
// V8 has a bug with properties named "__proto__"
// https://code.google.com/p/v8/issues/detail?id=621
var proto = JS('', '#.__proto__', e);
// __proto__ can be undefined on IE9.
if (JS('bool',
'typeof # !== "undefined" && # !== Object.prototype',
proto, proto)) {
map['__proto__'] = revive('__proto__', walk(proto));
}
return map;
}
return revive('', walk(json));
}