Gson exception - de-gson a complex object - java

I have an object that contains
public class PositionsChannelApplicationGroups {
public PositionsChannelApplicationGroups(){}
private Map<MyObj1, List<Character>> portfoliosToApplicationIds = new HashMap<MyObj1, List<Character>>();
private Map<MyObj1, List<Character>> accountsToApplicationIds = new HashMap<MyObj2, List<Character>>();
private Map<Character, List<MyObj1>> applicationIdToPortfolios = new HashMap<Character, List<MyObj1>>();
private Map<Character, List<MyObj2>> applicationIdToAccounts = new HashMap<Character, List<MyObj2>>();
}
Now I try to gson it at the server and de-gson it at the client.
To make it simple I get an exception when I do this in one line
Gson gson = new Gson();
gson.fromJson(gson.toJson(object), PositionsChannelApplicationGroups.class);
or even
gson.fromJson(gson.toJson(object), new TypeToken<PositionsChannelApplicationGroups>(){}.getType());
but it gives me the following exception (below ).
what am I doing wrong ?
com.google.gson.JsonParseException: Expecting object found: "MyObj1{hibernateID=0, portfolioName='MyString'}"
at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler(JsonObjectDeserializationVisitor.java:100)
at com.google.gson.ReflectingFieldNavigator.visitFieldsReflectively(ReflectingFieldNavigator.java:63)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:120)
at com.google.gson.JsonDeserializationContextDefault.fromJsonPrimitive(JsonDeserializationContextDefault.java:85)
at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:56)
at com.google.gson.MapTypeAdapter.deserialize(MapTypeAdapter.java:67)
at com.google.gson.MapTypeAdapter.deserialize(MapTypeAdapter.java:33)
at com.google.gson.JsonDeserializerExceptionWrapper.deserialize(JsonDeserializerExceptionWrapper.java:51)
at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:92)
at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler(JsonObjectDeserializationVisitor.java:117)
at com.google.gson.ReflectingFieldNavigator.visitFieldsReflectively(ReflectingFieldNavigator.java:63)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:120)

The Gson limitation you're bumping up against concerns how it serializes map keys: by calling toString(). From MapTypeAdapter:
map.add(String.valueOf(entry.getKey()), valueElement);
This behavior is also described in the MapTypeAdapter documentation.
This implementation really only works well with simple primitive types as the map key. If the key is not a simple primitive then the object is {#code toString}ed and that value is used as its key.
If you insist on using custom types as map keys, then as best I can tell you're going to have to write a custom serializer and/or a custom deserializer and/or a toString() that generates a string representation that's easy to deserialize.
Also, take a look at MapAsArrayTypeAdapter for one approach. (It's usable with a call to GsonBuilder.enableComplexMapKeySerialization(), not through direct instantiation as the docs describe (because it's currently not a public class). I didn't test it to see if its implementation works, but it looks promising.)
Note: The applicationIdToPortfolios and applicationIdToAccounts attributes serialize and deserialize simply without custom handling, since they are maps with primitive type keys.

Related

How to serialize JSON Array into Java HasMap, using specific field as a key with GSON?

For example, given JSON:
[
{"id":"3", "location":"NewYork", "date":"yesterday"},
{"id":"4", "location":"Moscow", "date":"today"}
]
resulting HashMap:
<"3", POJOLocation("NewYork", "yesterday")>
<"4", POJOLocation("Moscow", "today")>
where POJOLocation is a Java object:
class POJOLocation {
private String location;
private String date;
// etc
}
I've tried using custom deserializer, but it was really bloated with generic's tokens and hackish typeOfs. Perhaps there is a simple efficient solution?
Maybe create a POJOLocationId class:
class POJOLocationId {
private int id;
private String location;
private String date;
// etc
}
Then deserialize & loop over the resulting List populating your HashMap as you go?
Gson is designed to make serializing Java objects to their JSON equivalent painless. If you're trying to represent a Java data structure as a different type of JSON structure you're not going to have a lot of fun writing serializers and deserializers. At that point you might consider a lower-level JSON parser and simply implement the parsing you want yourself. Rather than representing your data one way in JSON and another way in Java (and thus running into the hassle of transforming between them) you might consider refactoring either your data structure or your data so they're more similar.
That said the easiest thing to do with Gson (which is really not that bad, memory and time-wise) is to use a wrapper type and then transform the input/output before using it. Something like so (borrowing from Tom Mac's type name):
private static final Type LIST_TYPE =
new TypeToken<List<POJOLocationId>>() {}.getType();
public String serialize(Map<Integer, POJOLocation> locations) {
List<POJOLocationId> locationsList = original.entrySet().stream()
.map(e -> new POJOLocationId(e.getKey(), e.getValue()).collect(toList());
return gson.toJson(locationsList);
}
public Map<Integer, POJOLocation> deserialize(String json) {
List<POJOLocationId> locationsList = gson.fromJson(json, LIST_TYPE);
return locationsList.stream()
.collect(toMap(l -> l.getId(), new POJOLocation(l)));
}
You certainly can get this same behavior with a custom deserializer, but this works, it's clean, and it's easy to read. The garbage collector should have no trouble cleaning up these temporary wrappers as soon as these methods return.

How to change Jackson's default concrete map type?

I have a very simple Jackson code to deserialize a nested JSON object:
public class MapTest
{
public static void main(String ... args) throws Exception
{
final String ser = "{\"nested\":{\"k1\":\"v1\",\"k2\":\"v2\",\"k3\":\"v3\"}}";
final Map<String, Object> deser = new ObjectMapper().readValue(ser, new TypeReference<TreeMap<String, Object>>(){});
System.out.println("Class of deser's nested object is " + deser.get("nested").getClass().getSimpleName());
}
}
When I run this I obtain the following output:
Class of deser's nested object is LinkedHashMap
However I want the nested map to be deserialized as a TreeMap rather than as a LinkedHashMap as per the output. How can I tell Jackson to default to use a TreeMap when deserializing?
Reading through the documentation the closest thing I found was the ability to define concrete classes for abstract types through addAbstractTypeMapping() in a module but I've tried every superclass and instance of LinkedHashMap in an attempt to do this and nothing seems to work.
This is using Jackson 2.4.2, although if there is a way to do this that requires a higher version I would be able to upgrade.
Module's addAbstractTypeMapping() is indeed the way to achive mapping in general. But the problem may be due to recursive nature of deserialization; because inner values are considered to be of type java.lang.Object.
However, I think there were indeed fixes to this part in 2.5, so I would specifically checking to see if 2.5.0 would work, once you add abstract mapping from Map to TreeMap.
If this does not work, please file a bug at https://github.com/FasterXML/jackson-databind/issues since it should work.

Serialization and Deserialization of generic Map for Java and external use

I have a Map<String, Object> which I am using as a mapping for a JSON document, however want to create and maintain Java type information at the same time as retaining the structure of the document.
I'm attempting to use Jackson to create the document and it seems to work fine but I'm seeing something strange when attempting to deserialize it. A very simple serialization example:
final ObjectMapper mapper = new ObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, JsonTypeInfo.As.EXTERNAL_PROPERTY);
final Map<String, Object> map = Maps.newHashMap();
map.put("test", new Date());
final String ser = mapper.writeValueAsString(map);
final Map<String, Object> deser = mapper.readValue(ser, new TypeReference<HashMap<String, Object>>(){});
System.err.println(deser.get("test").getClass());
Gives the serialized form {"test":1410721662084,"#class":"java.util.Date"} which seems fine but when deserializing returns the type of "test" to be Long.
If I change the type serialization to use WRAPPER_ARRAY rather than EXTERNAL_PROPERTY then the type of "test" is correctly returned as Date, but doing this alters the structure of the JSON document so is not something I'm allowed to do. How do I retain the structure of the document as well as allow deserialization back to the correct types?
This is against Jackson 2.4.2.
Deserialization with maps is always tricky as maps don't preserve type information, which makes Jackson resort to #class and that not something you usually want. Instead, you can create a simple class:
public class TestClass {
private Date test;
//getters and setters omitted
}
This class has concrete structure and JSON will serialize it as
{ "test" : 1410721662084}
which is much cleaner and type-safe representation of your object. Then you just need to pass TestClass.class to readValue() method and your test attribute will be magically converted to proper type (Date)

Using Gson how to add variable name as top level parameter for json of a REST call

I am trying call a REST service and using gson I am getting the following json for the following java pojo.
pojo
public class AlphaParameters {
private int one;
private int two;
private int three;
//getter setters
//constructors
}
Json
{"one":4,
"two":5,
"three":10
}
I am using the following code
Gson gson = new Gson()
AlphaParameters alphaParameters = new AlphaParameters(one,two,three);
gson.toJson(alphaParameters );
Earlier this code used to work, but now seems the server side which is on .net changed their implementation and now they are expecting the json in the following format. Everything is same but seems now they want the toplevel variable name in the json.
{"alphaParameters":
{"one":4,
"two":5,
"three":10
}
}
Question : Is there a specific api of Gson which I can use to generate the above json without refactoring my code ?
Or writing a wrapper class to include alphaParameters will be a better approach .
( I will have to write a lot of boilerplate code for latter ).
Thanks for your help.
I don't think Gson itself allows this kind of serialization but there is a number of ways you could tackle this problem without creating wrapper classes.
In my comment, I suggested putting the object in a map but that's a bit strange and you can do it so it looks more obvious in the code and probably performs better.
public Gson wrapJson(Object objectToSerialize) {
Gson gson = new Gson();
JsonObject result = new JsonObject();
//Obtain a serialized version of your object
JsonElement jsonElement = gson.toJsonTree(objectToSerialize);
result.add(objectToSerialize.getClass().getSimpleName(), jsonElement);
return result;
}
Then you can use it like this:
AlphaParameters alphaParameters = new AlphaParameters(one,two,three);
wrapJson(alphaParameters);
This allows you to use one pretty universal method in every case like this without writing boilerplate classes.
I used the class name to generate the key but feel free to modify this as it suits you. You could pass the key name as a parameter to make this wrapper utility more flexible.

An easy way to initialize all objects in a java class

I am trying to generate documentation for existing services. Its a bunch of rest services. What I would like to do is to simply create a new tag in xdoclet, that new tag will have a parameter. something like
#JSONInputMessage("com.foo.bar.input")
#JSONOutputMessage("com.foo.bar.output")
the xdoclet will then go to that class, initialize it, dump it into Jackson to convert it to json, and then copy the resulting json into the javadoc.
All of this is simple enough. My problem is i need a way to take in an object, then walk the fields all the way down, initializing the objects so they actually show up in the json dump. Does anyone have an easy way to reflect thru an object and initialize all objects?
Podam initializes a java object tree with random data, but you may define a strategy or use attributes to decide the values that are set.
Simple example:
PodamFactory factory = new PodamFactoryImpl(); //This will use the default Random Data Provider Strategy
Pojo myPojo = factory.manufacturePojo(Pojo.class);
Or with a strategy:
DataProviderStrategy strategy = new MyDataProviderStrategy();
PodamFactory factory = new PodamFactoryImpl(strategy);
Pojo myPojo = factory.manufacturePojo(Pojo.class);
Or with attributes:
#PodamStrategyValue(PostCodeStrategy.class)
private String postCode;
and then you define the strategy class for that single attribute:
public class PostCodeStrategy implements AttributeStrategy<String> {
public String getValue() throws PodamMockeryException {
...
}
}
Would this work for you?

Categories