Using different classes to map JSON data with GSON - java

My application is receiving JSON messages from a WebSocket connection.
There are different types of answers, which are formatted like that:
{
"type": "snapshot",
"product_id": "BTC-EUR",
"bids": [["1", "2"]],
"asks": [["2", "3"]]
}
or
{
"type": "l2update",
"product_id": "BTC-EUR",
"changes": [
["buy", "1", "3"],
["sell", "3", "1"],
["sell", "2", "2"],
["sell", "4", "0"]
]
}
... for example (see full API here).
Depending on the "type", I would like GSON to map a different class (e.g. Snapshot.class and l2update.class).
I have message handlers that subscribe to the WebSocket connection and I want the message to be processed by the relevant handler. For instance:
ErrorMessageHandler would manage the errors
SnapshotMessageHandler would create the initial order book
L2UpdateMessageHandler would update the order book
and so on
My problem is to dispatch the messages depending on their type.
I was thinking to convert them to the appropriate class and then call the relevant handler using a factory. I'm currently stuck at the first step, converting the JSON in Error.class or Snapshot.class depending on the "type".
How can I do that?

For Gson you could use com.google.gson.typeadapters.RuntimeTypeAdapterFactory.
Assuming you have - for example - following classes:
public class BaseResponse {
private String type, product_id;
// rest of the common fields
}
public class Snapshot extends BaseResponse {
// rest of the fields
}
public class L2Update extends BaseResponse {
// rest of the fields
}
then you would build following RuntimeTypeAdapterFactory:
RuntimeTypeAdapterFactory<BaseResponse> runtimeTypeAdapterFactory =
RuntimeTypeAdapterFactory
.of(BaseResponse.class, "type") // set the field where to look for value
.registerSubtype(L2Update.class, "l2update") // values map to 'type'
.registerSubtype(Snapshot.class, "snapshot");// value in json
Registering this with Gson will then enable automativcal instantiation of each type of responses:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();
and provide BaseResponse for fromJson(..) if using it , like:
gson.fromJson( json , BaseResponse.class);
NOTE: that Gson omits de- & serializing the type field. However it needs to be set in Json. Just as it is now in responses you get.

You may want to consider using a library that requires a bit less of a solid object model, at least at first. I use JsonPath for this type of thing. You could use it to at least find out the type you're dealing with:
String type = JsonPath.read(yourIncomingJson, "$.type");
and then, based on the string, do a switch statement as #ShafinMahmud suggests.
However, you could use JsonPath for the whole thing too. You could read all of the values using the path notation and know how to parse based on the type.
Adding another library to read a single value may or may not work for you but if you use it to read other values it might end up being worthwhile.

Related

Java POJO attributes mapping

I have a use case where I receive some attributes in the request like this,
"filters": [
{
"field": "fName",
"value": "Tom"
},
{
"field": "LName",
"value": "Hanks"
}
]
I don't have a model defined for this. I just receive these attributes in the request and fire a query on elastic search using these attributes. My records in elastic search have the same attribute names.
Now, I have to support a legacy application where attribute's names are completely different.
E.g.: fName becomes firstName and lName becomes lastName.
Problem: Need to accept old attribute names in the request, convert them to new ones so that it matches my elastic search records. Fetch the data with new attribute names and convert back to old ones before sending out the response from the application.
NOTE: I don't have POJO's defined for these records.
How can this be achieved effectively? I was thinking of using Orika mapper but not sure how that will work without defining classes first.
What prevents you from writing a transformer from request JSON to your normalized JSON?
The normal flow I can think of is:
Request JSON -> POJO -> POJO with normalized value -> Normalized JSON
So your POJO looks like:
public class Filter {
List<FieldFilter> filters;
public static class FieldFilter {
private String field;
private String value;
}
}
Now you will have a transformation map like:
Map<String, String> fieldNameMapping = new HashMap<>();
fieldNameMapping.put("fName", "firstName");
fieldNameMapping.put("firstName", "firstName");
// The process of populating this map can be done either by a static initializer, or config/properties reader
Then you transform your POJO:
Filter filterRequest;
List<FieldFilters> normlizedFilters =
filterReq.getFilters().stream()
.map(f -> new FieldFilter(fieldNameMapping.get(f.getField()), f.getValue())
.collect(toList());
Then convert the Filter class to your normalized JSON.
We have a similar scenario and we are using apache JOLT.If you want to try some samples, you can refer jolt-demo-online-utility
Use a JSON to JSON-transformer instead. Good answers regarding this can be found here: JSON to JSON transformer and here : XSLT equivalent for JSON
In the end you do not require an intermediate object type here. You even said, that you do not have such a type yet and inventing it, just to transform it, doesn't really make sense.

Rest Template binding with root array

I'm trying to use Rest Template to bind JSON to POJOs.
Imagine having a SpaceX class and a Rocket class, while the SpaceX class has a List<Rocket> attribute. I use the #JsonProperty annotation to let the Rest Template bind "Rocket ID" and "name" inside a Rocket object automatically.
My JSON file starts as array like following:
[
{
"Rocket ID": "1",
"name": "A"
},
{
"Rocket ID": "2",
"name": "B"
}
]
I'm able to consume the JSON file and fill a List<Rocket> manually like this:
public <T> List<T> createObjectsFromJSON(Class<T[]> responseType) {
ResponseEntity<T[]> responseEntity = restTemplate.exchange(URL, HttpMethod.GET, request, responseType);
T[] objects = responseEntity.getBody();
List<T> list = Arrays.asList(objects);
return list;
}
but I want to create a SpaceXobject and let the Rest Template fill in the List<Rocket> automatically.
I can't wrap my head around the answer on how to do it. I can't tell the Rest Template to bind the list via #JsonProperty, because there is no name.
If you can't change JSON structure than you have to write custom serialization and deserialization logic. You can use #JsonCreator and #JsonValue annotations:
The #JsonCreator annotation is used to tune the constructor/factory
used in deserialization. It’s very helpful when we need to deserialize
some JSON that doesn’t exactly match the target entity we need to get.
#JsonValue indicates a single method that should be used to serialize
the entire instance.
Or #JsonSerialize and #JsonDeserialize:
#JsonSerialize is used to indicate a custom serializer will be used to
marshall the entity.
#JsonDeserialize is used to indicate the use of a custom deserializer.
This article contains more details and examples: http://www.baeldung.com/jackson-annotations

Jackson - Use custom deserializer only for specific JSON

We have a class containing multiple Sets of Longs. We want them serialized as arrays, and most clients do so.
However, we have a PHP client that creates sets such that they serialize in an odd way. A set with the number 4 comes in like this:
"setOfNumbers": {
"4": 0
},
Naturally, Jackson complains about this being an object and not an array. What I would like is to have a custom deserializer that is only invoked if Jackson detects an object where a Set<Long> should be (and ideally only if they are contained in specific classes.)
I've tried this:
this.addDeserializer(Set.class, new StdDelegatingDeserializer<>(new StdConverter<Map<String, Long>, Set<Long>>() {
#Override
public Set<Long> convert(Map<String, Long> set) {
return parseLongs(set);
}
}));
The problem with this is that now it expects an object instead of an array for all Set fields. The class being deserialized is generated, so I can't add any annotations or make other changes.
If the generated class has always the same name you can try with Json Jackson Mix-in annotations as shown in this example

How to deserialize this json

This is the json response returned by MediaWiki API. I want to create a class to be able to deserialize it to it use Jackson library. The problem is that this json contains a key which is different from each request (here is 290).
{
"query-continue": {
"revisions": {
"rvcontinue": 633308090
}
},
"query": {
"pages": {
"290": {
"pageid": 290,
"ns": 0,
"title": "A",
"revisions": [
{
"user": "Mr. Guye",
"timestamp": "2014-12-07T17:45:55Z",
"comment": "comment",
"contentformat": "text/x-wiki",
"contentmodel": "wikitext",
"*": "content"
}
]
}
}
}
}
How could create a class (or configure the mapper) to be able to deserialize this json?
You can deserialize JSON to multiple formats using Jackson. One way that you mentioned is to convert the JSON to a POJO which may be difficult when the keys are dynamic. Another approach is to deserialize the JSON to the Jackson Tree Model which is called JsonNode. The following illustrates how you can parse the provided JSON to a JsonNode and then retrieve the various attributes.
final ObjectMapper mapper = new ObjectMapper();
// Parse the JSON, deserialize to the Tree Model
final JsonNode jsonNode = mapper.readTree(jsonString);
// Get hold of the "query -> pages" node.
final JsonNode pages = jsonNode.path("query").path("pages");
// Iterate the pages
for (final JsonNode page : pages) {
// Work with the page object here...
System.out.println(page.get("pageid")); // -> 290
}
The JsonNode object is very flexible and contains various convenience functions for accessing the data. As shown in the example above the path() and get() methods are two ways of accessing the data. If you use get() the property MUST exist, if you use path the property MAY exist. Furthermore, there are multiple ways of iterating the sub-elements and the loop shown above is one way.
Take a look at the Jackson docs for more info.
The short answer is you can't, at least not in the current format with that abominable asterisk being present. Therefore, we will have to employ a bit of hackery here to get the job done, and I warn you upfront, it's not going to be pretty.
Firstly, copy that response, then go to http://www.jsonschema2pojo.org/ and paste it into the JSON textbox. After pasting it, change the asterisk to something more civilized, like "content". Select JSON (default is JSON Schema) for the Source Type, input your package and root class name respectively, and click JAR to generate the package with all the POJO's that map to this JSON. You could also click "Preview" and copy paste the code into your source files -- it's really up to you.
Now that we have a valid version of this JSON structure, we use Jackson to read it in. If your JSON String is called jsonResponse and the corresponding POJO class is MediaWiki, then you convert it with Jackson like this:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MediaWiki mw = objectMapper.readValue(profileJson, MediaWiki.class);
The key here is the FAIL_ON_UNKNOWN_PROPERTIES being set to false, which means it will ignore that asterisk, and create everything else for you.
Now, to actually grab whatever value was present for that asterisk and store it into our "content" attribute (or whatever else you wanted replace the asterisk with), you are going to have to parse this sucker out client-side and pass it as a separate input parameter, and to do that, you will have to yank it out by calling something like this:
var content = query.pages.290.revisions["*"];
This content parameter is passed and stored it into your POJO's content attribute.
I know it's a lot of work, and if anyone else has a more elegant solution, please share. As I said, mine was not going to be pretty. :-)
This looks like key value pair.
You can use map in order to deserialize key value pairs:
public class Query {
private Map<Integer, Page> pages;
public Map<Integer, Page> getPages() {
return pages;
}
public void setPages(Map<Integer, Page> pages) {
this.pages = pages;
}
}
Jackson handles such deserialization by default.

Deserialize self referencing objects with Jackson

I have a JSON string looking like that (simplified):
[
{ "id":1, "friends":[2] },
{ "id":2, "friends":[1,3] },
{ "id":3, "friends":[] }
]
The content of friends are ids of other users in the list.
Is it possible somehow to create a Java class like the one below from the JSON just with Data Binding using Jackson or do I need an intermediate step for that?
public class User {
private long userid;
private List<User> friends;
// ... getters/setters
Thanks for your help.
There is no fully annotative way to do this, so you would need custom JsonSerializer / JsonDeserializer. Jackson 1.9 adds two new features that might help:
ValueInstantiators, so you can add constructors for deserializer to convert from basic integer into POJO
Value injection so you could pass additional context object (which you would need to find ids of already deserializer objects, to map then from integer to instance)
However I am not 100% sure how to combine these two features for specific use case...

Categories