Deserialize json with boolean or object field - java

I use Retrofit and GSON and I'm getting Json like this:
{
"firstName": "John",
"lastName": "Doe",
"passporRf": {
"number": "996633",
"series": "1111",
"code": "66666"
}
}
And when user doenst have a passport - this fields is boolean with "false" value.
How to deserialize it correctly and get boolean value - false, when this field is boolean and get JSON object when its object.
I found a JSONDeserializer but i cant to use it correctly. Code is:
public class DocumentDeserializer implements JsonDeserializer<Passport> {
#Override
public Passport deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonPrimitive primitive = json.getAsJsonPrimitive();
if (primitive.isBoolean()) {
// What value do i have to return here?
}
return context.deserialize(json, Passport.class);
}
}

Don't use GSon nor any other binding library. Nothing can be bound to "either a boolean or an object", so don't try.
Use a JSON parser, and look at the JSON content to obtain the information you want. Possibly build a User object out of the information you can find.

Code is given below,
Sample.class
import com.google.gson.Gson;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
/**
* Class containing news information in form of POJO.
*/
public class Sample {
public String firstName;
public String lastName;
private List<SampleOne> passporRf;
public List<SampleOne> getPassporRf() {
return passporRf;
}
private void setPassporRf(List<SampleOne> passporRf) {
this.passporRf = passporRf;
}
// Using custom DeSerializer for "multimedia" object since the API is just "Great"
public static class SampleDetailsDeSerializer implements JsonDeserializer<Sample> {
#Override
public Sample deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Sample accountState = new Gson().fromJson(json, Sample.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("passporRf")) {
JsonElement elem = jsonObject.get("passporRf");
if (elem != null && !elem.isJsonNull()) {
if(elem.isJsonPrimitive()){
accountState.setPassporRf(null);
} else {
final List<SampleOne> passporRfList = new Gson().fromJson(elem.getAsJsonArray().toString()
, new TypeToken<List<SampleOne>>(){}.getType());
accountState.setPassporRf(passporRfList);
}
}
}
return accountState ;
}
}
}
SampleOne.class
public class SampleOne {
public String number;
public String series;
public String code;
}
Hope it may help you.

Related

Different fields with same JsonProperty attribute

Is it possible to have something like below while serializing a JSON in the same class
#JsonProperty("stats")
private StatsDetails statsDetails
#JsonProperty("stats")
private List<StatsDetails> statsDetailsList
so i can have either statsDetails or statsDetailsList only one of these being included while forming a json.
I also have a separate JsonMapper code that transforms this pojo data into a json which i haven't included here.
You cannot do that. It will throw JsonMappingException jackson cannot know which of the fields are you referring to. You can try it by yourself with the following code:
POJOClass:
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.List;
public class POJOClass {
public POJOClass(String object) {
this.object = object;
}
public POJOClass(List<String> objectList) {
this.objectList = objectList;
}
#JsonProperty("object")
public String object;
#JsonProperty("object")
public List<String> objectList;
#JsonGetter("object")
public String getObject() {
return object;
}
#JsonGetter("object")
public List<String> getObjectList() {
return objectList;
}
#JsonSetter("object")
public void setObject(String object) {
this.object = object;
}
#JsonSetter("object")
public void setObjectList(List<String> objectList) {
this.objectList = objectList;
}
}
Main class:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class MainClass {
public static void main(String[] args) {
String text = "f";
List<String> list = Arrays.asList("a", "b", "c");
ObjectMapper mapper = new ObjectMapper();
try {
String json = mapper.writeValueAsString(new POJOClass(text));
String listJson = mapper.writeValueAsString(new POJOClass(list));
System.out.println("json=" + json);
System.out.println("listJson=" + listJson);
} catch (IOException e) {
e.printStackTrace();
}
}
}
The output:
com.fasterxml.jackson.databind.JsonMappingException: Multiple fields representing property "object": POJOClass#object vs POJOClass#objectList

Gson Serializer for a given class if in another specific class

I am trying to serialize (using Gson) a POJO and to have a special treatment for a single one of its fields.
Is it possible to do it in a simpler way than coding an adapter implementing JsonSerializer and having its serialize() method copy every field except for a specific one which receives the special treatment ?
Would it even be possible to make it using annotations in my POJO ?
I also cannot just write an adapter of the type of the specific field as it is a java.util.Date and I do not want every serialized Date to receive this treatment.
Here is an illustration :
public class Pojo {
#SerializedName("effectiveDate")
private final Date mDate;
#SerializedName("status")
private final Status mStatus; // <-- The field needing specific serialization
#SerializedName("details")
private final String mDetails;
// other fields
// methods
}
I would like to avoid coding an adapter as such :
public class PojoAdapter implements JsonSerializer<Pojo> {
#Override
public JsonElement serialize(final Pojo src, final Type typeOfSrc, final JsonSerializationContext context) {
final JsonObject jsonPojo = new JsonObject();
jsonDeployment.add("effectiveDate", /* special treatment */);
jsonDeployment.add("status", src.getStatus());
jsonDeployment.add("details", src.getDetails());
// other fields setting
return jsonPojo;
}
}
You can implement custom com.google.gson.JsonSerializer for a Date class and use com.google.gson.annotations.JsonAdapte annotation for given field to register it. See below example:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
import java.lang.reflect.Type;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class GsonApp {
public static void main(String[] args) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(new DatesPojo(new Date())));
}
}
class CustomDateJsonSerializer implements JsonSerializer<Date> {
#Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
String format = src.toInstant().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_TIME);
return new JsonPrimitive(format + " ISO TIME");
}
}
class DatesPojo {
#JsonAdapter(CustomDateJsonSerializer.class)
#SerializedName("customDate")
private final Date mDate0;
#SerializedName("effectiveDate")
private final Date mDate1;
public DatesPojo(Date mDate) {
this.mDate0 = mDate;
this.mDate1 = mDate;
}
public Date getmDate0() {
return mDate0;
}
public Date getmDate1() {
return mDate1;
}
}
Above code prints:
{
"customDate": "22:37:21.806+01:00 ISO TIME",
"effectiveDate": "Jan 22, 2020 10:37:21 PM"
}
I found another solution which consists in making my Date field implement an EffectiveDate interface which just extends Date and to add an adapter for this single field.

Deserialize to String or Object using Jackson

I have an object that sometimes looks like this:
{
"foo" : "bar",
"fuzz" : "bla"
}
and sometimes looks like this:
{
"foo" : { "value" : "bar", "baz": "asdf" },
"fuzz" : { "thing" : "bla", "blip" : "asdf" }
}
these classes would look like:
public class Foo {
String value;
String baz;
}
public class Fuzz {
String thing;
String blip;
}
where the first cases are shorthand for the second ones. I would like to always deserialize into the second case.
Further - this is a pretty common pattern in our code, so I would like to be able to do the serialization in a generic manner, as there are other classes similar to Foo above that have the same pattern of using String as a syntactic sugar for a more complex object.
I'd imagine the code to use it would look something like this
public class Thing {
#JsonProperty("fuzz")
Fuzz fuzz;
#JsonProperty("foo")
Foo foo;
}
How do I write a custom deserializer (or some other module) that generically handles both cases?
To make it generic we need to be able to specify name which we would like to set in object for JSON primitive. Some flexibility gives annotation approach. Let's define simple annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface JsonPrimitiveName {
String value();
}
Name means: in case primitive will appear in JSON use value() to get property name for given primitive. It binds JSON primitive with POJO field. Simple deserialiser which handles JSON object and JSON primitive:
class PrimitiveOrPojoJsonDeserializer extends JsonDeserializer implements ContextualDeserializer {
private String primitiveName;
private JavaType type;
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(type);
if (p.currentToken() == JsonToken.START_OBJECT) {
return deserializer.deserialize(p, ctxt);
} else if (p.currentToken() == JsonToken.VALUE_STRING) {
BeanDeserializer beanDeserializer = (BeanDeserializer) deserializer;
try {
Object instance = beanDeserializer.getValueInstantiator().getDefaultCreator().call();
SettableBeanProperty property = beanDeserializer.findProperty(primitiveName);
property.deserializeAndSet(p, ctxt, instance);
return instance;
} catch (Exception e) {
throw JsonMappingException.from(p, e.getMessage());
}
}
return null;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
JsonPrimitiveName annotation = property.getAnnotation(JsonPrimitiveName.class);
PrimitiveOrPojoJsonDeserializer deserializer = new PrimitiveOrPojoJsonDeserializer();
deserializer.primitiveName = annotation.value();
deserializer.type = property.getType();
return deserializer;
}
}
Now we need to annotate POJO fields as below:
class Root {
#JsonPrimitiveName("value")
#JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Foo foo;
#JsonPrimitiveName("thing")
#JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Fuzz fuzz;
// getters, setters
}
I assume that all classes are POJO-s and follow all rules - have getters, setters and default constructor. In case constructor does not exist you need to change this beanDeserializer.getValueInstantiator().getDefaultCreator().call() line somehow which fits your requirements.
Example app:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, Root.class));
}
}
Prints for shortened JSON:
Root{foo=Foo{value='bar', baz='null'}, fuzz=Fuzz{thing='bla', blip='null'}}
And for full JSON payload:
Root{foo=Foo{value='bar', baz='asdf'}, fuzz=Fuzz{thing='bla', blip='asdf'}}

How do I override a Java map when converting a JSON to Java Object using GSON?

I have a JSON string which looks like this:
{
"status": "status",
"date": "01/10/2019",
"alerts": {
"labels": {
"field1": "value1",
"field2": "value2",
"field3": "value3",
"field100": "value100"
},
"otherInfo" : "other stuff"
},
"description": "some description"
}
My corresponding Java classes look like the following:
public class Status {
private String status;
private String date;
private Alerts alerts;
private String description;
}
And
public class Alerts {
private Map<String, String> labels;
private String otherInfo;
public Map<String, String> getLabels() {
return labels();
}
}
I'm parsing the given JSON into Java object using this:
Status status = gson.fromJson(statusJSONString, Status.class);
This also gives me Alerts object from Status class:
Alerts alerts = status.getAlerts();
Here is my problem:
Let's consider the labels:
I want to make keys in the label map the case-insensitive. So for example, if the provided key/value pair is "field1" : "value1", or "Field1" : "value1" or "fIeLD1":"value1", I want to be able to retrieve them by simply calling alerts.getLabels.get("field1").
Ideally, I want to set the keys to be lowercase when the labels map is originally created. I looked into Gson deserialization examples, but I'm not clear exactly how to approach this.
There isnt really much you can do here. Even if you extended HashMap, the problem is that when the JSON is de-serialized, it doesn't call native methods. What you COULD do is the following, but it is rather cumbersome:
import java.util.HashMap;
public class HashMapCaseInsensitive extends HashMap<String, String> {
private boolean convertedToLower = false;
#Override
public String put(String key, String value) {
if(!convertedToLower){
convertToLower();
}
return super.put(key.toLowerCase(), value);
}
#Override
public String get(Object key) {
if(!convertedToLower){
convertToLower();
}
return super.get(key.toString().toLowerCase());
}
private void convertToLower(){
for(String key : this.keySet()){
String data = this.get(key);
this.remove(key);
this.put(key.toLowerCase(), data);
}
convertedToLower = true;
}
}
You can write your own MapTypeAdapterFactory which creates Map always with lowered keys. Our adapter will be based on com.google.gson.internal.bind.MapTypeAdapterFactory. We can not extend it because it is final but our Map is very simple so let's copy only important code:
class LowercaseMapTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<String> stringAdapter = gson.getAdapter(TypeToken.get(String.class));
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) { }
#Override
public T read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<String, String> map = new HashMap<>();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
String key = stringAdapter.read(in).toLowerCase();
String value = stringAdapter.read(in);
String replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
in.endObject();
return (T) map;
}
};
}
}
Now, we need to inform that our Map should be deserialised with our adapter:
class Alerts {
#JsonAdapter(value = LowercaseMapTypeAdapterFactory.class)
private Map<String, String> labels;
private String otherInfo;
// getters, setters, toString
}
Assume that our JSON payload looks like below:
{
"status": "status",
"date": "01/10/2019",
"alerts": {
"labels": {
"Field1": "value1",
"fIEld2": "value2",
"fielD3": "value3",
"FIELD100": "value100"
},
"otherInfo": "other stuff"
},
"description": "some description"
}
Example usage:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Gson gson = new GsonBuilder().create();
Status status = gson.fromJson(new FileReader(jsonFile), Status.class);
System.out.println(status.getAlerts());
}
}
Above code prints:
Alerts{labels={field1=value1, field100=value100, field3=value3, field2=value2}, otherInfo='other stuff'}
This is really tricky solution and it should be used carefully. Do not use this adapter with much complex Map-es. From other side, OOP prefers much simple solutions. For example, create decorator for a Map like below:
class Labels {
private final Map<String, String> map;
public Labels(Map<String, String> map) {
Objects.requireNonNull(map);
this.map = new HashMap<>();
map.forEach((k, v) -> this.map.put(k.toLowerCase(), v));
}
public String getValue(String label) {
return this.map.get(label.toLowerCase());
}
// toString
}
Add new method to Alerts class:
public Map<String, String> toLabels() {
return new Labels(labels);
}
Example usage:
status.getAlerts().toLabels()
Which gives you a very flexible and secure behaviour.
Though this is not a very generic solution, however, I think this will serve your purpose.
I would like to suggest you create an adapter for Gson which can convert the map values for you. The adapter might look like the following.
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
final class GSONAdapter implements JsonDeserializer<String> {
private static final GSONAdapter instance = new GSONAdapter();
static GSONAdapter instance() {
return instance;
}
#Override
public String deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
// Here I am taking the elements which are starting with field
// and then returning the lowercase version
// so that the labels map is created this way
if (jsonElement.getAsString().toLowerCase().startsWith("field"))
return jsonElement.getAsString().toLowerCase();
else return jsonElement.getAsString();
}
}
Now just add the GsonBuilder to your Gson using the adapter and then try to parse the JSON. You should get all the values in the lower case as you wanted for the labels.
Please note that I am just taking the field variables in my concern and hence this is not a generic solution which will work for every key. However, if your keys have any specific format, this can be easily applied.
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, GSONAdapter.instance())
.create();
Status status = gson.fromJson(statusJSONString, Status.class);
Alerts alerts = status.getAlerts();
Hope that solves your problem.

How to create model class for json parsing?

I need to create the model class for the following type of json:
{
"AdditinalInfo": [
{
"Tag": "ORDER",
"Value": "[{\"EN_CODE\":\"8901233014951\",\"SKU_CODE\":\"1000003\",\"SKU_DESC\":\"5Star crunchy chocolate 33g\" ,\"QUANTITY\":\"1\",\"UNIT_SELLING_PRICE\":\"18.0\"}]"
}
]
}
Please help how can I create model class for the above json. I need to send the json using the POST method.
use
http://www.jsonschema2pojo.org/
delete json which shows its a dummy copy past json to json place
click Preview and then finally download zip
done.
Thanks
You can generate model from json automatically using online tools like THIS
-----------------------------------com.example.AdditinalInfo.java-----------------------------------
package com.example;
import javax.annotation.Generated;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
#Generated("org.jsonschema2pojo")
public class AdditinalInfo {
#SerializedName("Tag")
#Expose
public String tag;
#SerializedName("Value")
#Expose
public String value;
}
-----------------------------------com.example.Example.java-----------------------------------
package com.example;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
#Generated("org.jsonschema2pojo")
public class Example {
#SerializedName("AdditinalInfo")
#Expose
public List<AdditinalInfo> additinalInfo = new ArrayList<AdditinalInfo>();
}
class AdditinalInfo {
public TagValuesPair[] AdditinalInfo;
}
class TagValuesPair {
public String Tag;
public Map<String, String> Value;
}
// getter setter constructors are ommitted for simplicity
You don't need to create model for sending this Json via Retrofit.
#POST("/save")
Call<JsonElement> request(#Body RequestBody requestBody);
String resultJson = ... // your json
//parse it to RequestBody type
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), resultJson);
//create adapter
Retrofit restAdapter = new Retrofit.Builder()
.baseUrl(Constants.ROOT_API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
IConstructSecureAPI service = restAdapter.create(IConstructSecureAPI.class);
Call<JsonElement> result = service.CreateAccount(requestBody);
result.enqueue(new Callback<JsonElement>() {
#Override
public void onResponse(Call<JsonElement> call, retrofit2.Response<JsonElement> response) {
if(response.isSuccessful()){
JsonElement jsonElement = response.body();
JsonObject withResponse = jsonElement.getAsJsonObject();
}
else{
System.out.println(response.message());
}
}
#Override
public void onFailure(Call<JsonElement> call, Throwable t) {
}
});
{} braces are for object and [] are Arrays .
For example,
class ModelClass{
public ArrayList<ADDITIONALINFOCLASS> AdditinalInfo;
public class ADDITIONALINFOCLASS {
public String Tag;
public String Value;
}
}
The error that you are getting is for wrong parsing, try this code and see if it works.

Categories