5

I'm currently trying to use Flexjson to deserialize a JSON String and map it to the Object model of my Android App. The application is a kind of library with several vendors that can have some catalogs with more catalogs and documents in them. The json is fetched from a web service I have no influence on and looks something like this:

{
    "library":{
        "vendor":[
            {
                "id":146,
                "title":"Vendor1",
                "catalog":[
                     {
                        "id":847,
                        "document":[
                            {
                                "id":1628,
                                "title":"Document",
                ...
                            },
                            {
                ...
                            }
                        ],
                        "title":"Catalog ",
                    },
                {
                ...
                }
              ]
            },
            {
            ...
            }
        ]
    }
}

So each vendor, catalog, document is represented by a JSONObject and all child catalogues and documents are within a JSONArray. So far everything works fine with Flexjson and the following deserialization code:

LibraryResponse response = new JSONDeserializer<LibraryResponse>()
                    .use(Timestamp.class, new TimestampObjectFactory())
                    .deserialize(getLocalLibrary(), LibraryResponse.class);
return response.library;

I do have a Library object that has a List<Vendor>. Each vendor has a List<Catalog> and a List<Document>.

But unfortunately, the web service straps the JSONArrays to simple JSONObjects if a catalog contains only a single document or a catalog contains just one catalog. So the json in that case looks like this:

"document":
    {
        "id":1628, 
        "title":"Document",
        ...
    }

Now Flexjson doesn't know how to deserialize and I end up with a library.vendorX.getDocument() being a List<HashMap> instead of a List<Document>. One idea is to tell Flexjson explicitly how to handle such cases, but I have no idea where to start with this. Another way could be to parse the initial json manually and replace such JSONObjects with the appropriate JSONArray. But I think that way is not really nice to go, as the library can be pretty deep.

I hope you can provide some guidance here.

RvdK
  • 19,580
  • 4
  • 64
  • 107
lcs.bdr
  • 388
  • 1
  • 9

1 Answers1

1

Yikes this is some gnarly json mapping going on. What backend coder did that?! #NotHelping.

Well from looking at the code, Flexjson is coded to handle this out of the box. But it looks like it's not passing the typing information down to the bind so it doesn't know what type it's binding into so it just returns a Map. That's a bug that should probably be fixed. Good news is there is a work around.

Anyway, the simplest thing I can think of is to install an ObjectFactory on that list. Then you can check and see if you get a Map or a List when the stream is deserialized. Then you can wrap it in a List and send it on to the appropriate decoder. Something like:

LibraryResponse response = new JSONDeserializer<LibraryResponse>()
                .use(Timestamp.class, new TimestampObjectFactory())
                .use("library.vendor.values.catalog.values.document", new ListDocumentFactory() )
                .deserialize(getLocalLibrary(), LibraryResponse.class);

Then

public class ListDocumentFactory implements ObjectFactory {

     public Object instantiate(ObjectBinder context, Object value, Type targetType, Class targetClass) {
         if( value instanceof Collection ) {
             return context.bindIntoCollection((Collection)value, new ArrayList(), targetType);
         } else {
             List collection = new ArrayList();
             if( targetType instanceof ParameterizedType ) {
                 ParameterizedType ptype = (ParameterizedType) targetType;
                 collection.add( context.bind(value, ptype.getActualTypeArguments()[0]) );
             } else {
                 collection.add( context.bind( value ) );
                 return collection;
             }
         }
     }
}

I think that's roughly what would fix that bug, but should also fix your problem.

chubbsondubs
  • 37,646
  • 24
  • 106
  • 138
  • Thanks for your answer! I think I made myself not clear, I always get a List but in case there is only one document I don't have `Documents` in the List but `HashMaps`, so `List` as `catalog.document` instead of `List`. The hash map objects contain the data connected to the document. Seems strange to me. I think I need a Factory that decides if we already have a Document or if we have a `HashMap`. If the latter I would map the document object manually. My first tries failed, though. Do you think this approach is possible? – lcs.bdr Oct 31 '12 at 22:48
  • I fail to see how my answer didn't answer your problem. I think the reason you are getting a List instead of List is a bug in Flexjson. JSONDeserializer first translates the JSON into simple objects like List, Map, String, Number, and Boolean. Then it uses a series of ObjectFactory to map those simple objects into the actual objects you are mapping into. So the ObjectFactory layer is before we have a any strong typed object like Document. So you'll plug into that point writing your own ObjectFactory like I showed instead of using the default instance. – chubbsondubs Nov 01 '12 at 02:11
  • I'm sorry, I think I misunderstood you there. Anyway, when trying you code I'm getting an Exception at `ParameterizedType ptype = (ParameterizedType) targetType;` `java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType`. Another question: If I specify the Factory to be used on `library.vendor.values.catalog.values.document` it won't affect other levels as `library.vendor.values.document`? As even a vendor can have documents... Would it be possible to specify the factory to be used on `LibraryItem.class` which is the superclass of all the others? – lcs.bdr Nov 01 '12 at 10:24
  • Specifying by object path will only use that ObjectFactory for that path (unless you use wildcards). You can set ObjectFactories by class, but in this case you'd need to set it on all List.class. The good news is you could set it on all List.class because the code isn't tied to any specifics about Document. If you are getting an exception are you using generics on your instance variables? For example List? If not then Flexjson won't know what type of object to map the JSON to within the list. – chubbsondubs Nov 01 '12 at 14:37
  • The use of the Factory on the `List.class` did the trick finally! Thank you very much for your great support. Flexjson is great, even when dealing with nasty backend developers ;-). – lcs.bdr Nov 01 '12 at 16:52