77import 'dart:_js_helper' show patch;
88import 'dart:_foreign_helper' show JS;
99import 'dart:_interceptors' show JSExtendableArray;
10+ import 'dart:_internal' show MappedIterable;
11+ import 'dart:collection' show Maps, LinkedHashMap;
1012
1113/**
1214 * Parses [json] and builds the corresponding parsed JSON value.
@@ -37,7 +39,11 @@ _parseJson(String source, reviver(key, value)) {
3739 throw new FormatException (JS ('String' , 'String(#)' , e));
3840 }
3941
40- return _convertJsonToDart (parsed, reviver);
42+ if (reviver == null ) {
43+ return _convertJsonToDartLazy (parsed);
44+ } else {
45+ return _convertJsonToDart (parsed, reviver);
46+ }
4147}
4248
4349/**
@@ -46,7 +52,6 @@ _parseJson(String source, reviver(key, value)) {
4652 * in-place.
4753 */
4854_convertJsonToDart (json, reviver (key, value)) {
49-
5055 var revive = reviver == null ? (key, value) => value : reviver;
5156
5257 walk (e) {
@@ -59,7 +64,8 @@ _convertJsonToDart(json, reviver(key, value)) {
5964 // TODO(sra): Replace this test with cheaper '#.constructor === Array' when
6065 // bug 621 below is fixed.
6166 if (JS ('bool' , 'Object.getPrototypeOf(#) === Array.prototype' , e)) {
62- var list = JS ('JSExtendableArray' , '#' , e); // Teach compiler the type is known.
67+ // Teach compiler the type is known by passing it through a JS-expression.
68+ var list = JS ('JSExtendableArray' , '#' , e);
6369 // In-place update of the elements since JS Array is a Dart List.
6470 for (int i = 0 ; i < list.length; i++ ) {
6571 // Use JS indexing to avoid range checks. We know this is the only
@@ -95,6 +101,232 @@ _convertJsonToDart(json, reviver(key, value)) {
95101 return revive (null , walk (json));
96102}
97103
104+ _convertJsonToDartLazy (object) {
105+ // JavaScript null and undefined are represented as null.
106+ if (object == null ) return null ;
107+
108+ // JavaScript string, number, bool already has the correct representation.
109+ if (JS ('bool' , 'typeof # != "object"' , object)) {
110+ return object;
111+ }
112+
113+ // This test is needed to avoid identifing '{"__proto__":[]}' as an array.
114+ // TODO(sra): Replace this test with cheaper '#.constructor === Array' when
115+ // bug https://code.google.com/p/v8/issues/detail?id=621 is fixed.
116+ if (JS ('bool' , 'Object.getPrototypeOf(#) !== Array.prototype' , object)) {
117+ return new _JsonMap (object);
118+ }
119+
120+ // Update the elements in place since JS arrays are Dart lists.
121+ for (int i = 0 ; i < JS ('int' , '#.length' , object); i++ ) {
122+ // Use JS indexing to avoid range checks. We know this is the only
123+ // reference to the list, but the compiler will likely never be able to
124+ // tell that this instance of the list cannot have its length changed by
125+ // the reviver even though it later will be passed to the reviver at the
126+ // outer level.
127+ var item = JS ('' , '#[#]' , object, i);
128+ JS ('' , '#[#]=#' , object, i, _convertJsonToDartLazy (item));
129+ }
130+ return object;
131+ }
132+
133+ class _JsonMap implements LinkedHashMap {
134+ // The original JavaScript object remains unchanged until
135+ // the map is eventually upgraded, in which case we null it
136+ // out to reclaim the memory used by it.
137+ var _original;
138+
139+ // We keep track of the map entries that we have already
140+ // processed by adding them to a separate JavaScript object.
141+ var _processed = _newJavaScriptObject ();
142+
143+ // If the data slot isn't null, it represents either the list
144+ // of keys (for non-upgraded JSON maps) or the upgraded map.
145+ var _data = null ;
146+
147+ _JsonMap (this ._original);
148+
149+ operator [](key) {
150+ if (_isUpgraded) {
151+ return _upgradedMap[key];
152+ } else if (key is ! String ) {
153+ return null ;
154+ } else {
155+ var result = _getProperty (_processed, key);
156+ if (_isUnprocessed (result)) result = _process (key);
157+ return result;
158+ }
159+ }
160+
161+ int get length => _isUpgraded
162+ ? _upgradedMap.length
163+ : _computeKeys ().length;
164+
165+ bool get isEmpty => length == 0 ;
166+ bool get isNotEmpty => length > 0 ;
167+
168+ Iterable get keys {
169+ if (_isUpgraded) return _upgradedMap.keys;
170+ return _computeKeys ().skip (0 );
171+ }
172+
173+ Iterable get values {
174+ if (_isUpgraded) return _upgradedMap.values;
175+ return new MappedIterable (_computeKeys (), (each) => this [each]);
176+ }
177+
178+ operator []= (key, value) {
179+ if (_isUpgraded) {
180+ _upgradedMap[key] = value;
181+ } else if (containsKey (key)) {
182+ _setProperty (_processed, key, value);
183+ _setProperty (_original, key, null ); // Reclaim memory.
184+ } else {
185+ _upgrade ()[key] = value;
186+ }
187+ }
188+
189+ void addAll (Map other) {
190+ other.forEach ((key, value) {
191+ this [key] = value;
192+ });
193+ }
194+
195+ bool containsValue (value) {
196+ if (_isUpgraded) return _upgradedMap.containsValue (value);
197+ List <String > keys = _computeKeys ();
198+ for (int i = 0 ; i < keys.length; i++ ) {
199+ String key = keys[i];
200+ if (this [key] == value) return true ;
201+ }
202+ return false ;
203+ }
204+
205+ bool containsKey (key) {
206+ if (_isUpgraded) return _upgradedMap.containsKey (key);
207+ if (key is ! String ) return false ;
208+ return _hasProperty (_original, key);
209+ }
210+
211+ putIfAbsent (key, ifAbsent ()) {
212+ if (containsKey (key)) return this [key];
213+ var value = ifAbsent ();
214+ this [key] = value;
215+ return value;
216+ }
217+
218+ remove (Object key) {
219+ if (! _isUpgraded && ! containsKey (key)) return null ;
220+ return _upgrade ().remove (key);
221+ }
222+
223+ void clear () {
224+ if (_isUpgraded) {
225+ _upgradedMap.clear ();
226+ } else {
227+ if (_data != null ) {
228+ // Clear the list of keys to make sure we force
229+ // a concurrent modification error if anyone is
230+ // currently iterating over it.
231+ _data.clear ();
232+ }
233+ _original = _processed = null ;
234+ _data = {};
235+ }
236+ }
237+
238+ void forEach (void f (key, value)) {
239+ if (_isUpgraded) return _upgradedMap.forEach (f);
240+ List <String > keys = _computeKeys ();
241+ for (int i = 0 ; i < keys.length; i++ ) {
242+ String key = keys[i];
243+ f (key, this [key]);
244+
245+ // Check if invoking the callback function changed
246+ // the key set. If so, throw an exception.
247+ if (! identical (keys, _data)) {
248+ throw new ConcurrentModificationError (this );
249+ }
250+ }
251+ }
252+
253+ String toString () => Maps .mapToString (this );
254+
255+
256+ // ------------------------------------------
257+ // Private helper methods.
258+ // ------------------------------------------
259+
260+ bool get _isUpgraded => _processed == null ;
261+
262+ Map get _upgradedMap {
263+ assert (_isUpgraded);
264+ return _data;
265+ }
266+
267+ List <String > _computeKeys () {
268+ assert (! _isUpgraded);
269+ List keys = _data;
270+ if (keys == null ) {
271+ keys = _data = _getPropertyNames (_original);
272+ }
273+ return keys;
274+ }
275+
276+ Map _upgrade () {
277+ if (_isUpgraded) return _upgradedMap;
278+
279+ // Copy all the (key, value) pairs to a freshly allocated
280+ // linked hash map thus preserving the ordering.
281+ Map result = {};
282+ List <String > keys = _computeKeys ();
283+ for (int i = 0 ; i < keys.length; i++ ) {
284+ String key = keys[i];
285+ result[key] = this [key];
286+ }
287+
288+ // We only upgrade when we need to extend the map, so we can
289+ // safely force a concurrent modification error in case
290+ // someone is iterating over the map here.
291+ if (keys.isEmpty) {
292+ keys.add (null );
293+ } else {
294+ keys.clear ();
295+ }
296+
297+ // Clear out the associated JavaScript objects and mark the
298+ // map as having been upgraded.
299+ _original = _processed = null ;
300+ _data = result;
301+ assert (_isUpgraded);
302+ return result;
303+ }
304+
305+ _process (String key) {
306+ if (! _hasProperty (_original, key)) return null ;
307+ var result = _convertJsonToDartLazy (_getProperty (_original, key));
308+ return _setProperty (_processed, key, result);
309+ }
310+
311+
312+ // ------------------------------------------
313+ // Private JavaScript helper methods.
314+ // ------------------------------------------
315+
316+ static bool _hasProperty (object, String key)
317+ => JS ('bool' , 'Object.prototype.hasOwnProperty.call(#,#)' , object, key);
318+ static _getProperty (object, String key)
319+ => JS ('' , '#[#]' , object, key);
320+ static _setProperty (object, String key, value)
321+ => JS ('' , '#[#]=#' , object, key, value);
322+ static List _getPropertyNames (object)
323+ => JS ('JSExtendableArray' , 'Object.keys(#)' , object);
324+ static bool _isUnprocessed (object)
325+ => JS ('bool' , 'typeof(#)=="undefined"' , object);
326+ static _newJavaScriptObject ()
327+ => JS ('=Object' , 'Object.create(null)' );
328+ }
329+
98330@patch
99331class _Utf8Encoder {
100332 // Use Uint8List when supported on all platforms.
0 commit comments