Wednesday, October 2, 2013

Generic Json Mapper(Part 1)

I finished the json mapper implementation based on restricted (my) mirror library.
 The idea is by providing a target class information for a given Json string, it automatically creates the instance of the given class filled with the json data.

Following is the sample codes:
 final Map<Type, ConstructorFun> entityCtors = {
    ExpenseType: (Map map)=>new ExpenseType(map['name'], map['code'])};

  final Map<Type, StringifierFun> stringifiers = {
    ExpenseType: (ExpenseType et) => '{"name": "${et.name}", "code": "${et.code}"}'};

var jsonMapper = new JsonMapper(new SpecialTypeMapHandler(entityCtors, stringifiers: stringifiers));

Expense e = new Expense.Default()
  ..id = "aaa"
  ..rev = "mmm"
  ..amount = 111
  ..date = new DateTime.now()
  ..detail = "asdjah"
  ..isClaimed = true
  ..expenseType = const ExpenseType("Hotel", "HT");

  String json = jsonMapper.toJson(obj);
  print(">> json: ${json}");
  Object obj = jsonMapper.fromJson(Expense, json);
  print(">> obj: ${obj}");
 
I have not yet finished two cases, when attribute has List or Map value.
I will do it later.
But actually there are a bit of difficulty for these cases.
In order to get List type's parameter type information, the current simple mirror API must be extended.
And a list may contains an instance of the different subclass of List's element type.
Map has a similar problem.
Also even if the field type is defined as ordinary class, the actual instance may be of subclass of that class.
So the current implementation is not covering such cases(yet).
But if we know all the available classes at the run time, we may be able to uniquely identify appropriate class. anyway if there are some ambiguity, that is inherent to the given json itself.

Also if we provide possible subclass information in the list value property as annotation, we can determine the class  efficiently.

This code was actually done by extending existing dart:json library. so it covers quite rigorously the spec of json.(but in fact, it uses old version of json code. what is the difference between the latest and older code?

This code does ot suport List, Map properties yet..
library json_mapper_v1;

import "dart:json";

import 'package:portable_mirror/mirror_api_lib.dart';

part 'src/dart_JsonStringifier.dart'; // this should be eliminated, dart:json should have pblic class JsonStringifier instead of _JsonStringifier

typedef dynamic ConstructorFun(Map map);
typedef dynamic StringifierFun(Object obj);
typedef dynamic ConvertFun(Object obj);

abstract class ISpecialTypeMapHandler {
  ConstructorFun entityCtor(Type type);
  StringifierFun stringifier(Type type);
  ConvertFun convert(Type type);
}

class SpecialTypeMapHandler implements ISpecialTypeMapHandler {
  final Map entityCtors;
  final Map _converts = {DateTime: (Object value)=>DateTime.parse(value)};
  final Map _stringifiers = {DateTime: (DateTime dt) => '"${dt.toString()}"'};
  
  SpecialTypeMapHandler(this.entityCtors, {Map converts, Map stringifiers}) {
    if (converts != null) _converts.addAll(converts);
    if (stringifiers != null) _stringifiers.addAll(stringifiers);
  }
  
  ConstructorFun entityCtor(Type type)=>(entityCtors == null)?null:entityCtors[type];
  ConvertFun convert(Type type)=>_converts[type];
  StringifierFun stringifier(Type type)=>_stringifiers[type];
}

//
//
//
abstract class IJsonMapper {
  Object fromJson(Type modelType, String json);
  
  String toJson(final object, {StringSink output});
}

class JsonMapper implements IJsonMapper {
  EntityJsonParser parser;
  EntityJsonStringifier stringifier;
  ISpecialTypeMapHandler mapHandler;
  
  JsonMapper(this.mapHandler, {_Reviver reviver}) {
    parser = new EntityJsonParser(mapHandler, reviver: reviver);
    stringifier = new EntityJsonStringifier(mapHandler);
  }

  Object fromJson(Type modelType, String json) => parser.parse(modelType, json);
  String toJson(final object, {StringSink output}) => stringifier.toJson(object, output: output);
}

//
// json parser
//
typedef _Reviver(var key, var value);

class EntityJsonParser {
  //EntityBuildJsonListener listener; // TODO.. this would be effcient way..
  ISpecialTypeMapHandler mapHandler;
  _Reviver _reviver;
  
  EntityJsonParser(this.mapHandler, {_Reviver reviver}) {
    _reviver = reviver;
  }
  
  EntityBuildJsonListener getListener(Type modelType) =>(_reviver == null)?new EntityBuildJsonListener(mapHandler, modelType)
      :new EntityReviverJsonListener(mapHandler, modelType, _reviver);

  dynamic parse(Type modelType, String json) { 
    EntityBuildJsonListener listener =  getListener(modelType);
    new JsonParser(json, listener).parse();
    return listener.result;
  }
}

class EntityBuildJsonListener extends BuildJsonListener {
  final ISpecialTypeMapHandler mapHandler;
  IClassMirror currentCmirror = null;
  List cmirrorStack = [];
  
  EntityBuildJsonListener(this.mapHandler, Type modelType) {
    currentCmirror = ClassMirrorFactory.reflectClass(modelType);
  }
  
  /** Pushes the currently active container (and key, if a [Map]). */
  void pushContainer() {
    super.pushContainer();
    cmirrorStack.add(currentCmirror);
  }

  /** Pops the top container from the [stack], including a key if applicable. */
  void popContainer() {
    super.popContainer();
    currentCmirror = cmirrorStack.removeLast();
  }
  
  void beginObject() {
    super.beginObject();
    if (key != null) {
      IFieldType ft = currentCmirror.fieldTypes[new Symbol(key)];
      if (ft != null) {
        currentCmirror = ClassMirrorFactory.reflectClass(ft.type);
      } else {
        print('>> beginObject ${key}');
        currentCmirror = null;
      }
    }
  }

  void endObject() {
    Map map = currentContainer;
    ConstructorFun spCtor = mapHandler.entityCtor(currentCmirror.type);
    if (spCtor != null) {
      currentContainer = spCtor(map);
    } else {
      // Dart Beans
      IInstanceMirror imiror = currentCmirror.newInstance();
      currentCmirror.fieldTypes.forEach((_, IFieldType ft){
        ConstructorFun vCtor = mapHandler.convert(ft.type);
        var value = map[ft.name];
        imiror.getField(ft.symbol).value = (vCtor != null)?vCtor(value):value;
      });
      currentContainer = imiror.reflectee;
    }
    super.endObject();
  }
}

class EntityReviverJsonListener extends EntityBuildJsonListener {
  final _Reviver reviver;
  EntityReviverJsonListener(ISpecialTypeMapHandler mapHandler, Type modelType, reviver(key, value))
    : super(mapHandler, modelType), this.reviver = reviver;

  void arrayElement() {
    List list = currentContainer;
    value = reviver(list.length, value);
    super.arrayElement();
  }

  void propertyValue() {
    value = reviver(key, value);
    super.propertyValue();
  }

  get result {
    return reviver("", value);
  }
}

No comments:

Post a Comment