JSON Deep Mapping - java

Is it possible to map a field which is deeper in a json-response to a property in an object - in other words: transform a json which hierarchy into a flat object?
For example I would like to annotate the 'user_id' property of the Marker class with 'links.user.id'.
I have looked into GSON and Jackson, but couldn't find a solution.
Json-Response for a Marker:
{
"id": 791,
"name": "Marker42",
"links": {
"user": {
"href": "http://4242.com/users/970",
"id": 970
}
}
Data-Model:
public class Marker {
#SerializedName("id")
private int id;
#SerializedName("name")
private String name;
#SerializedName("links.user.id")
private int user_id;
}

This isn't pretty but you can set your own deserialiser in GSON. I am not as familiar with Jackson but this tutorial shows a very similar method: http://www.baeldung.com/jackson-deserialization
public static class MarkerGSONDeserializer implements JsonDeserializer<Marker>{
#Override
public Marker deserialize(JsonElement data, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {
if(!data.isJsonObject()){
return null;
} else {
JsonObject obj = data.getAsJsonObject();
Marker res = new Marker();
res.setId(obj.get("id").getAsInt());
res.setName(obj.get("name").getAsString());
res.setUserId(((obj.get("links").getAsJsonObject())).get("user").getAsJsonObject()).get("id").getAsInt();
return res;
}
}
}

Related

Parsing polymorphic object with objectmapper

I am trying to parse a polymorphic object (it is from the Strava api) with a jackson object mapper. The object looks like this:
[
{
"type": "latlng",
"data": [
[ 38.603734, -122.864112 ],
[ 38.608798, -122.867714 ],
... omitted for brevity ...
[ 38.611205, -122.870848 ],
[ 38.603579, -122.863891 ]
],
"series_type": "distance",
"original_size": 512,
"resolution": "low"
},
{
"type": "distance",
"data": [
0.0,
1305.8,
... omitted for brevity ...
128136.6,
129444.1
],
"series_type": "distance",
"original_size": 512,
"resolution": "low"
}
]
So based on the type the field data has a different object in it. In most cases it is an array of floats. In the case of the "latlng", there is an array of float[], so it is a float[][] (I would think).
I create an object that represents this data structure, with a deserializer. It looks like this:
public class StravaStream {
#JsonProperty("type")
private String type;
public String getType() {
return type;
}
public static class StravaStream1D extends StravaStream {
#JsonProperty("data")
private float[] data;
public StravaStream1D() {
}
public float[] getData() {
return data;
}
}
public static class StravaStream2D extends StravaStream {
#JsonProperty("data")
private float[][] data;
public StravaStream2D() {
}
public float[][] getData() {
return data;
}
}
public StravaStream() {
}
public static class StravaStreamDeserializer extends StdDeserializer<StravaStream> {
public StravaStreamDeserializer() {
super(StravaStream.class);
}
#Override
public StravaStream deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Class<? extends StravaStream> variantStravaStream;
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
JsonNode type = root.get("type");
System.out.println("type is "+type);
if (type.textValue().equals("latlng")) {
variantStravaStream = StravaStream2D.class;
} else {
variantStravaStream = StravaStream1D.class;
}
System.out.println("variant is "+variantStravaStream.getSimpleName());
return mapper.readValue(jp, variantStravaStream);
}
}
}
When I only ask for one dimensional data, like distance objects or so, it works out fine. But when I try to parse the "latlng" float[][], jackson fails. I am sure that the type is recognized, see the additonal system.out, it prints that a StravaStream2D.class variant is used.
The error message (and the additional system out) I get is:
01-26 09:05:49.605 27165-27165/nl.jfvh.stravatest I/System.out: type is "latlng"
01-26 09:05:49.605 27165-27165/nl.jfvh.stravatest I/System.out: variant is StravaStream2D
01-26 09:05:49.620 27165-27165/nl.jfvh.stravatest W/System.err: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of float[] out of VALUE_NUMBER_FLOAT token
01-26 09:05:49.620 27165-27165/nl.jfvh.stravatest W/System.err: at [Source: java.io.StringReader#e8550ec; line: 1, column: 40164] (through reference chain: java.util.ArrayList[0]->nl.jfvh.stravatest.client.model.StravaStream2D["data"]->[Ljava.lang.Object[][0])
Is my data model wrong? The parsing of polymorphic objects is very new for me, I followed some tutorials, but the problems seems to be in the simple mapping of the data on the float[][]...
Since (according to you) you are new to parsing polymorphic objects, I'd suggest using annotations instead of a custom deserialiser. It's as simple as:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(name = "latlng", value = StravaStream.StravaStream2D.class),
#JsonSubTypes.Type(name = "distance", value = StravaStream.StravaStream1D.class)
})
public class StravaStream {
Read docs for JsonTypeInfo and JsonSubTypes
An issue with the custom deserialiser you're trying with is that when you do return mapper.readValue(jp, variantStravaStream); at the end it will actually re-enter the deserializer because class StravaStream2D extends StravaStream so Jackson will use the custom deserializer again. I got stack overflow errors when trying this due to infinite recursion.

Apache Camel - GSON JsonSerializer use on routes

I have a endpoint with Camel that returns properties as JSON but are not with the proper order. The return class has a superclass that returns some control data which is necessarily to be present in every return.
public class Respuesta implements Serializable {
#SerializedName("subject")
#Expose
private String subject;
#SerializedName("action")
#Expose
private String action;
#SerializedName("status")
#Expose
private Integer status;
#SerializedName("description")
#Expose
private String description;
...getter/setter
And the final return class inherits that piece.
public class FacturadoresListarResponse extends Respuesta implements Serializable {
#SerializedName("lst")
#Expose
private List<Facturador> listaProveedores;
public FacturadoresListarResponse(List<Facturador> listaProveedores) {
super();
this.listaProveedores = listaProveedores;
}
public FacturadoresListarResponse() {
}
public void setRespuesta(Respuesta rsp) {
super.setAction(rsp.getAction());
super.setDescription(rsp.getDescription());
super.setStatus(rsp.getStatus());
super.setSubject(rsp.getSubject());
}
getter/setter...
}
So, the Gson's Marshaller takes first the inherited class property (lst), and then the parent class properties (subject, status, etc.), giving this kind of result on the wire.
{
"lst": [
{
"rut": "XXXX-X",
"rzsoc": "XXXXXXx",
"res": 1,
"ema": "a#a.cl"
}
],
"subject": "facturadores",
"action": "listar",
"status": 0,
"description": "OK"
}
I wrote a GSON custom JsonSerializer that builds data in order, but I can't use in a Camel DSL syntax. I tried, but without results:
.marshal().json(JsonLibrary.Gson,FacturadoresListarRspSerializer.class, true)
.convertBodyTo(String.class, "UTF-8")
Is there supported by Camel to use these kind of serializers to achieve proper order without migrating to Jackson?
Note: The code of the serializer (FacturadoresListarRspSerializer.class).
public class FacturadoresListarRspSerializer implements JsonSerializer<FacturadoresListarResponse> {
#Override
public JsonElement serialize(FacturadoresListarResponse src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("subject", src.getSubject());
jsonObject.addProperty("action", src.getAction());
jsonObject.addProperty("status", src.getStatus());
jsonObject.addProperty("description", src.getDescription());
final JsonArray jsarrFacturadores = new JsonArray();
for (final Facturador fact : src.getListaProveedores()) {
JsonObject jsobFacturadores = new JsonObject();
jsobFacturadores.addProperty("rut", fact.getRutCompleto());
jsobFacturadores.addProperty("rzsoc", fact.getRazonSocial());
jsobFacturadores.addProperty("res", fact.getResolucion());
jsobFacturadores.addProperty("ema", fact.getCorreoEnvio());
jsarrFacturadores.add(jsobFacturadores);
}
jsonObject.add("lst", jsarrFacturadores);
return jsonObject;
}
}
Create a new GSON instance:
Gson gson = new GsonBuilder().registerTypeAdapter(FacturadoresListarResponse.class,
new FacturadoresListarRspSerializer()).create();
Create a new GsonDataFormat by specifying the previously created Gson instance:
GsonDataFormat gsonDataFormat = new GsonDataFormat(gson, FacturadoresListarResponse.class);
Specify the previous data format in your RouteBuilder's marshal(DataFormat dataFormat) method:
.marshal(gsonDataFormat)

How to write a TypeAdapter for multiple JSON objects with Gson?

I have a JSON feed with multiple nested JSON objects. I have written my POJO classes and have looked on here for how to deserialize nested JSON objects to access the data I needed. However I am still receiving NullPointerExceptions on my nested JSON objects:
JSON feed example
{
"data": [
{
"relationships": {
"dismissals": {
"meta": {
"count": {
"home": 0,
"away": 0
}
},
"data": []
},
"home": {
"data": {
"type": "teams",
"id": "2"
}
}
}
}
]
}
Pojo Mappings
Relationships:
public class Relationships implements Serializable
#SerializedName("region")
#Expose
private Region region;
#SerializedName("competition")
#Expose
private Competition competition;
getters and setters
}
Region:
public class Region implements Serializable
{
#SerializedName("data")
#Expose
private Data data;
}
Data
public class Data implements Serializable, Parcelable
{
#SerializedName("type")
#Expose
private String type;
#SerializedName("id")
#Expose
private String id;
}
My TypeAdapter
public class ItemTypeDataFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}}
Retrofit builder:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new ItemTypeDataFactory()) // This is the important line ;)
.setDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
RequestInterface request = retrofit.create(RequestInterface.class);
For example I want to get:
getRelationships().getDissmissals().Meta().Count().Home();
When I run my app I get NullPointerException for that.
Is there something I need to add to my type adapter to deserialize the classes so I can get the data in multiple nested JSON objects? I have tried looking on here already and nothing has helped.
Some times it's difficult to generate the POJO class for a JSON response.
You can generate your POJO easily here http://www.jsonschema2pojo.org/

Json Serialize and Deserialize complex content using Gson

I have a structure that I want to store using JSON in a file. None of the implementation classes will have more significant information than what is given.
public class ItemExample implements IItem{
private ModelMap map;
private String name;
}
public class ModelMap {
private HashMap<Coord, IPartType> map;
}
public class Coord {
private int x,y,z;
}
public class PartExample implements IPartType {
private String name;
private Purity purity;
}
public Enum Purity{
}
I am brand new to creating JSONs, I've been reading up on how Gson works but I am not really understanding how to translate the examples to my case. Most examples assume knowledge of certain aspects that I just don't know yet.
This is what I have started to do for IPartType:
public class PartDeserialize<T> implements JsonDeserializer<T>{
#Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject content = json.getAsJsonObject();
String name = content.get("name").getAsString();
Purity purity = Purity.valueOf(content.get("purity").getAsString());
return new Gson().fromJson(content, typeOfT);
}
}
I would appreciate any help.
Possible JSON Example per request:
{
"name" : "sword",
"map" :
{
"map" :
{
"1,1,1" : //string representation of Coord
{
"name" : "blade",
"purity" : "base" // string representation of Purity Enum
},
"0,0,0" :
{
"name" : "handle",
"purity" : "high"
}
}
}
}

Deserialize Generic class Jackson or Gson

From the land of .NET I have a generic class define like so..
public class SyncWrapper<T, I>
{
public IList<T> Data { get; set; }
public IList<I> DeleteIds { get; set; }
public DateTime LastSyncDateTime { get; set; }
}
I was able to create an instance of this object from json by simply calling ...
JsonConvert.DeserializeObject<SyncWrapper<T, Guid>>(json);
Now I've been given the task of porting this code over to Java/Android. Having never touched Java before, I've a lot to learn!
Anyway, so far I've tried Gson and Jackson to get the object from json but no joy. I think that I won't be able to call andthing with the <T> involved gson.fromJson(json, SyncWrapper<T, UUID>.class) for example as there is a problem with type Erasure!
My efforts so far have looked like this....
Gson
Gson gson = new Gson();
SyncWrapper<MyClass, UUID> result = gson.fromJson(json, new TypeToken<SyncWrapper<MyClass, UUID>>() { }.getType());
This compiles but the result is an empty SyncWrapper
Jackson
ObjectMapper mapper = new ObjectMapper();
SyncWrapper<MyClass, UUID> result = mapper.readValue(json, new TypeReference<SyncWrapper<MyClass, UUID>>() { });
This compiles but crashes the app when executed!!!
My Java version of SyncWrapper....
public class SyncWrapper<T, I> {
private DateTime lastSyncDateTime;
private Collection<T> data;
private Collection<I> deleteIds;
public Collection<T> getData() {
return data;
}
public void setData(Collection<T> data) {
this.data = data;
}
public Collection<I> getDeleteIds() {
return deleteIds;
}
public void setDeleteIds(Collection<I> deleteIds) {
this.deleteIds = deleteIds;
}
public DateTime getLastSyncDateTime() {
return lastSyncDateTime;
}
public void setLastSyncDateTime(DateTime lastSyncDateTime) {
this.lastSyncDateTime = lastSyncDateTime;
}
}
I've been really thrown in at the deep end by the powers that be (all programming is the same isn't it?), so any help really appreciated.
I'm not precious about which library I use (Gson, Jackson, etc)
Update
An example of the Json that is to be deserialized...
{
"Data": [
{
"Name": "Company A",
"Id": "7d5d236c-c2b5-42dc-aea5-99e6752c8a52"
},
{
"Name": "Company B",
"Id": "44444444-0000-0000-0000-444444444444"
},
{
"Name": "Company C",
"Id": "249a4558-05c6-483f-9835-0056804791c9"
}
],
"DeleteIds": [
"5f7873a6-b2ee-4566-9714-1577b81384f4",
"1f224a39-16c3-441d-99de-8e58fa8f31c2"
],
"LastSyncDateTime": "\/Date(1393580073773+0000)\/"
}
..or this (more often than not, the DeleteIds will be null)...
{
"Data": [
{
"Name": "Company A",
"Id": "7d5d236c-c2b5-42dc-aea5-99e6752c8a52"
},
{
"Name": "Company B",
"Id": "44444444-0000-0000-0000-444444444444"
},
{
"Name": "Company C",
"Id": "249a4558-05c6-483f-9835-0056804791c9"
}
],
"DeleteIds": null,
"LastSyncDateTime": "\/Date(1393580073773+0000)\/"
}
For the above json I would be mapping to a SyncWrapper where T is Company...
public class Company extends ModelBase {
private String name;
public Company(UUID id, String name) {
super(id);
setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Here's the issues:
Your field names in your Java classes don't match the field names in the JSON; capitalization matters. This is why you're getting back absolutely nothing after parsing.
I'm going to go with Gson examples simply because I know that off the top of my head. You can do the same things in Jackson, but I'd need to look them up:
public class SyncWrapper<T, I> {
#SearializedName("LastSyncDateTime")
private DateTime lastSyncDateTime;
#SearializedName("Data")
private Collection<T> data;
#SearializedName("DeleteIds")
private Collection<I> deleteIds;
This tells Gson which fields in Java map to the fields in JSON. You could also go with a field naming policy instead, since it looks like all your fields are upper camel case:
Gson g = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.build();
Now your fields will match up. The next issue is going to be that UUID class. That class in Java is not a string; it's a class that generates UUIDs. Just use String for the type that holds it in your Java class.
The DateTime class ... same issue. And on top of that you've got a bit of a weird value in your JSON for the date. You'll either want to store that as a String as well, or you're going to have to write a custom deserializer to deal with it.
With those changes, I think you're good to go.
Edit to add from the comments: If you really need the Java UUID class rather than just the String representation, you can write a chunk of code that takes care of this for you:
class UUIDDeserializer implements JsonDeserializer<UUID>
{
#Override
public UUID deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
return UUID.fromString(je.getAsString());
}
}
You can then register this with Gson:
Gson g = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.registerTypeAdapter(UUID.class, new UUIDDeserializer())
.build();
This will populate the UUID typed fields in your class with UUID instances. This is the same thing you'd need to do with that funky date value.
I suggest using Jackson for this; it has a more clear API and does not require creating a new type as Gson (where you have to extend a class to be able to do that).
Example:
public static <T> T fromJsonToGenericPojo(
String json, Class<?> classType, Class<?>... genericTypes) {
JavaType javaType = TypeFactory.defaultInstance()
.constructParametricType(classType, genericTypes);
try {
return OBJECT_MAPPER.readValue(json, javaType);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
throw new IllegalArgumentException(e);
}
}

Categories

Resources