Thursday, October 3, 2013

Generic Json Mapper(Part 2)

In the previous post, I showed how to generate entity object from json string and type information.
Although I have already written the reverse direction(generating json from entity object),  it was written from scratch(since it was simple), and I wanted to see how I can use dart:json library in a similar way.

Then I found there is a class _JsonStringifier in json.dart in Dart sdk lib.
But as the name shows, it is private class, so I cannot extend the class and override some of the methods. If it is possible, I can do the similar things.
 That was a bit of disappointment. Actually the class  should be changed to public class.

By the way, I found Dartson was posted just 1 hour after I posted to Dartisans.
I think Json is quite universal data structure in web application, and this kind of binding are very powerful tool.

For the Dartson, I clone the project, and saw the code.
It is well written, although it is not yet supporting for javascript mode usage, it seemed planed to support it.

Essentially the main difference between our approaches is that  I took 'SAX' parser approach, and Dartson took DOM node approach. But right now, I just extended the library class, during the parsing it still creates maps, and uses the map to create an entity, then the map is immediately discarded.

If we use this approach, such an intermediate map creation can be avoided. We can just use stack to push all the key, value objects.
If we use such approach, it will be faster than Dartson.

Since Dartson is actually using json library to create intermediate json object(consisting List, Map, primitive values), the parsing quality will be as good as dart json library.

But Dartson seems not supporting irregular cases, like DateTime parsing/serialization, none Bean like classes where we need to call special factory/constructor(like ExpenseType).

I think it is better to have an ability to adjust such exceptional cases, otherwise, the tool's applicability will be unnecessarily limited.

//
// entity(including list, map) stringifier
//
class EntityJsonStringifier extends _JsonStringifier {
  final ISpecialTypeMapHandler mapHandler;
  
  EntityJsonStringifier(this.mapHandler): super(null);
  
  String toJson(final obj, {StringSink output}) {
     this..sink = (output != null)?output:new StringBuffer()
    ..seen = [];
    stringifyValue(obj);
    return sink.toString();
  }

  // @Override
  void stringifyValue(final object) {
    if (!stringifyJsonValue(object)) {
      checkCycle(object);
      try {
        // if toJson is defined, it will be used.
        var customJson = object.toJson();
        if (!stringifyJsonValue(customJson)) {
          throw new JsonUnsupportedObjectError(object);
        }
      } catch (e) {
        // if toJson is not defined..
        if (!stringifyJsonValue(object)) {
          stringifyEntity(object);
        }
      }
      seen.removeLast();
    }
  }
  
  void stringifyEntity(final object) {
    // this require dirt:mirrors
    Type t = ClassMirrorFactory.getType(object);
    StringifierFun stringfier = mapHandler.stringifier(t);
    if (stringfier != null) {
      sink.write(stringfier(object));
      return;
    }
    
    //
    IClassMirror cmirror = ClassMirrorFactory.reflectClass(t);
    IInstanceMirror iimirr = cmirror.reflect(object);
    
    sink.write('{');
    int idx = 0;
    Map fmap = cmirror.fieldTypes;
    int lastIdx = fmap.length-1;
    fmap.forEach((k, IFieldType ft){
      sink.write('"${ft.name}": ');
      stringifyValue(iimirr.getField(k).value);
      sink.write((idx == lastIdx)?"":",");
      idx++;
    });
    sink.write('}');   
  }
}

No comments:

Post a Comment