Closed ErikGrimes closed 10 years ago
I tried to change _jsonToObject
to the following so that all complex objects, including List
and Map
, will be instantiated via mirror.newInstance(_EMTPY_SYMBOL, [])
, which invokes the default constructor with no arguments.
_jsonToObject(json, mirror) {
if (json == null) return null;
var convert = jsonToObjects[mirror.reflectedType];
if (convert != null) return convert(json);
if (_isPrimitive(json)) return json;
TypeMirror type;
var instance = mirror.newInstance(_EMTPY_SYMBOL, []);
var reflectee = instance.reflectee;
if (reflectee is List) {
type = mirror.typeArguments.single;
for (var value in json) {
reflectee.add(_jsonToObject(value, type));
}
} else if (reflectee is Map) {
type = mirror.typeArguments.last;
for (var key in json.keys) {
reflectee[key] = _jsonToObject(json[key], type);
}
} else {
// TODO: Consider using [mirror.instanceMembers].
var setters = _getPublicSetters(mirror);
for (var key in json.keys) {
var name = new Symbol(key);
var decl = setters[name];
if (decl != null) {
type = decl.type;
} else {
decl = setters[new Symbol('$key=')];
if (decl != null) {
type = decl.parameters.first.type;
} else {
continue;
}
}
instance.setField(name, _jsonToObject(json[key], type));
}
}
return reflectee;
}
However, an exception occurs when decoding a List
. The problem is because the actual List type is _GrowableList
, which doesn't have a default constructor. Interestingly, when looking at the code of _GrowableList
, a default constructor was used!!!
List < T > result = new _GrowableList < T >();
The two solutions I can think of right now are
_GrowableList
_jsonToObject
to handle List
// dart:core-patch_growable_array.dart
factory _GrowableList(int length) {
var data = new _List((length == 0) ? 4 : length);
var result = new _GrowableList < T >.withData(data);
if (length > 0) {
result._setLength(length);
}
return result;
}
factory _GrowableList.withCapacity(int capacity) {
var data = new _List((capacity == 0) ? 4 : capacity);
return new _GrowableList < T >.withData(data);
}
factory _GrowableList.from(Iterable < T > other) {
List < T > result = new _GrowableList < T >();
result.addAll(other);
return result;
}
factory _GrowableList.withData(_List data)
native "GrowableList_allocate";
import 'package:observe/observe.dart';
import 'dart:mirrors';
main() {
var list = [
<int>[],
<int, int>{},
new ObservableList<int>(),
new ObservableMap<int, int>()
];
for (var e in list) {
var t = e.runtimeType;
var m = reflectType(t);
print('type: $t');
print('mirror: $m');
print('');
}
}
Output
type: List<int>
mirror: ClassMirror on '_GrowableList'
type: _LinkedHashMap<int, int>
mirror: ClassMirror on '_LinkedHashMap'
type: ObservableList<int>
mirror: ClassMirror on 'ObservableList'
type: ObservableMap<int, int>
mirror: ClassMirror on 'ObservableMap'
I went down the same path with _jsonToObject yesterday and ran into the same issue. There's seems to be a more general issue of default factory constructors which return implementations that don't have default constructors, which is the case with the core List implementation. The mirrors api has no know knowledge of the actual constructor that was used to create the object it is reflecting on. The sdk uses factory constructors to hide internal implementations in a number of places, so List is only the first of many potential problems.
I'm wondering if maybe the way to go is to allow the user to specify or provide a constructor somehow and pre-populate that registry to handle the sdk?
Jackson (https://github.com/FasterXML/jackson-databind) and other similar libraries may be able to provide some inspiration. The serialization package may also be worth a look. It has to handle similar issues.
I can add the following helper class to the library
class Typer<T> {
Type get type => T;
}
Then, this works.
List<String> list = decode('["green", "yellow", "orange"]',
type: new Typer<List<String>>().type);
Would that work with this?
class Example {
List<String> strings;
List<int> ints
}
Sure.
Sent from my Windows Phone
From: ErikGrimesmailto:notifications@github.com Sent: 5/14/2014 11:22 PM To: jolleekin/jsonxmailto:jsonx@noreply.github.com Cc: jolleekinmailto:jolleekin@outlook.com Subject: Re: [jsonx] Support ObservableList and ObservableMap (#3)
Would that work with this?
class Example {
List<String> strings;
List<int> ints
}
Reply to this email directly or view it on GitHub: https://github.com/jolleekin/jsonx/issues/3#issuecomment-43102995
Your example works as Example
is not a generic type. The purpose of TypeHelper
(renamed from Typer) is to deal with factory constructors of the built-in types such as List
// Doesn't work since the runtime type of <int>[] is _GrowableList<T>, not List<T>.
var x = decode('[1, 2, 3]', type: <int>[].runtimeType);
// Works as [type] is List<int>.
var x = decode('[1, 2, 3]', type: new TypeHelper<List<int>>().type);
// A generic class whose default constructor is NOT a factory constructor.
class ListResponse<T> {
List<T> items;
int total;
}
// Works as [runtimeType] is ListResponse<int>.
var x = decode('{items: [1, 2, 3], total: 10}', type: new ListResponse<int>().runtimeType);
// Works as [type] is ListResponse<int>.
var x = decode('{items: [1, 2, 3], total: 10}', type: new TypeHelper<ListResponse<int>>().type);
As you can see, runtimeType
only works with types that don't use factory constructors while TypeHelper
works with any type. Therefore, it is better to use TypeHelper
.
I just published another library called hotkey
(http://pub.dartlang.org/packages/hotkey, https://github.com/jolleekin/hotkey). Please try it out and let me know what you think. Thanks.
Thanks for the fix!
Using ObservableList and ObservableMap properties results in the wrong type getting created and a runtime exception being thrown (Exception: type '_LinkedHashMap' is not a subtype of type 'ObservableMap).