The more I work with Clojure, the more I realize that treating data as Maps is a scalable approach... However, there are times when I miss the explicitness of Java.
Today was nice because I got to integrate the wonderful flexibility of maps with the robustness of java beans.
I recently found the need to read in Jsons in an explicit object (thats easy using the Jackson library).
However, sometimes, your JSon data will have extra fields in it. For example, maybe my data service adds a new field to my phonebook entries , to include emails :
That is, it used to be :
{"number":"123-456-7890"};
And I had a bean : class MyPhoneBookEntry { public void setNumber(String s){...}...}. And everything was perfect.
But then one day my data service decided to add more fields, which I didnt care about :
{"number":"123-456-7890", "email":"jayunit100@xxx.yyy"}.
So how do we accomodate this email field ? Well, with jackson, we can use the @JSonAnySetter annotation, which will call a "default" setter which runs when a key name doesn't match any of the java declared getters and setters. This setter will, for example, dump extra fields (i.e. email) data into a map, called "unknownParameters".
There is - however - a problem with this. When we go to Re-Serialize the data, we have added a an unwanted side-effect - it has a new map called "unknownParameters" , so the reserialized map is different (structurally) then the input map.
A solution ? Simply use Jackson to
1) Create a map from your "core" java bean. This will be a subset of the data. This is done by calling:
myNewMap = m.convertValue(....)
2) Add the fields from your unknown parameters map, manually, to this map. This is simply a call to
myNewMap.putAll( unknownParametersMap )
3) Now, rather than serializing the bean - serialize this map.
That is, we simply build an intermediary map using reflection. This can easily be done using inheritance and then tested. Here is a test to confirm that this actually works.
First, create an Example bean class with 3 fields, name, id, primitiveId. Then you can simply run the following.
//given m, an ObjectMapper ; and a "createMapForSeraialization" method which does steps 1/2/3 above....... the following will work.
final String input = "{\"name\":\"Jay\",\"id\":100,\"primitiveId\":100,\"extraField\":\"laly\"}";
final ExampleBean dataAsBean = m.readValue(input, ExampleBean.class);
final Map explicitAndUnkownProps = dataAsBean.createMapForSerialization();
final Map<String,Object> explicitProps = m.convertValue(dataAsBean, Map.class);
Assert.assertTrue(explicitAndUnkownProps.size()>explicitProps.size());
Yay for JSONs, Maps, and reflection !
Neat! One additional note: you can also use @JsonAnyGetter to indicate reverse direction, for outputting "extra" properties without adding separate JSON Object container.
ReplyDeleteooooh wait okay, so reverse the direction will extract map parameters for me ? yay well thats nice to know :)
Delete