Jackson property order based on field value - java

I am trying to serialise and deserialise a Java class using Jackson and the JsonPropertyOrder depends on the value of the version field in the class. If version = 1, I want order to be {"start1", "start2"}, if version = 2, order should be {"end1", "end2"}.
I have below class:
#Builder
#Value
#AllArgsConstructor(onConstructor=#__(#JsonCreator))
#JsonPropertyOrder(custom property order depending on version field)
public class ClassA {
#NonNull Integer version;
#NonNull String start1;
#NonNull String start2;
#NonNull String end1;
#NonNull String end2;
}
How can I define the JsonPropertyOrder based on version on runtime. If I should use a custom Deserializer, I cannot figure out how exactly it should be implemented and set with the ObjectMapper.
This is the code for deserialisation:
private final ObjectMapper objectMapper = new ObjectMapper();//have initialzed this as a bean
String jsonStr = "{\"version\":1, \"startLat\":\"47.6812\", \"startLng\":\"-122.3268\", \"endLat\":\"47.6074\", \"endLng\":\"-122.3377\"}";
ClassA objA = null;
try {
objA = objectMapper.readValue(jsonStr, ClassA.class);
} catch (IOException e) {
log.error("Error deserializing the string", jsonStr, e);
}
return objA;
**EDIT: I missed an imp part. The property order here matters because the serialised string might not have the field names. Is that possible to do?
So the str [1, "47.6812", "-122.3268"] will need to be deserialised to the fields version, start1, start2.

If these properties are exclusive why do not create more concise POJO by removing two of them? You can use knowledge that 1 means something different than 2 by introducing some isMethod-es or Enum. To serialise class as JSON array you need to use #JsonFormat(shape = JsonFormat.Shape.ARRAY) annotation. To deserialise you can use 3-arg constructor with #JsonCreator and #JsonProperty annotations. Properties are final. All together makes - class is well implemented. See example:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Objects;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ClassA classA1 = new ClassA(1, "Start_1", "Start_2");
ClassA classA2 = new ClassA(2, "End_1", "End_2");
String json1 = mapper.writeValueAsString(classA1);
String json2 = mapper.writeValueAsString(classA2);
System.out.println(json1);
System.out.println(json2);
System.out.println(mapper.readValue(json1, ClassA.class));
System.out.println(mapper.readValue(json2, ClassA.class));
}
}
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
class ClassA {
private final Integer version;
private final String value1;
private final String value2;
#JsonCreator
public ClassA(#JsonProperty("version") Integer version,
#JsonProperty("start1") String value1,
#JsonProperty("start2") String value2) {
Objects.requireNonNull(version);
if (!(version == 1 || version == 2)) {
throw new IllegalArgumentException("Version is not supported!");
}
this.version = version;
this.value1 = value1;
this.value2 = value2;
}
#JsonIgnore
public boolean isStart() {
return version == 1;
}
#JsonIgnore
public boolean isEnd() {
return version == 2;
}
public Integer getVersion() {
return version;
}
public String getValue1() {
return value1;
}
public String getValue2() {
return value2;
}
#Override
public String toString() {
return "ClassA{" +
"version=" + version +
", value1='" + value1 + '\'' +
", value2='" + value2 + '\'' +
", isEnd()='" + isEnd() + '\'' +
", isStart()='" + isStart() + '\'' +
'}';
}
}
Above code prints:
[1,"Start_1","Start_2"]
[2,"End_1","End_2"]
ClassA{version=1, value1='Start_1', value2='Start_2', isEnd()='false', isStart()='true'}
ClassA{version=2, value1='End_1', value2='End_2', isEnd()='true', isStart()='false'}

Related

Jackson read-only property in method

I need to exposes a property in my json that will be processed in the getter method.
The class:
public class Configuracao{
private String departamento;
public String getDepartamento(){/**getter code**/}
public void setDepartamento(String departamento){/**setter code**/}
public String getDepartamentos(){/***Some logic code***/}
}
The json that got in front: {departamento: "Lote", departamentos: "Lotes"}
Works fine in serialization, but when my front-end post the json back, jackson throws a unrecognized field exception caused by 'departamentos'. How can I tell that I just want to 'departamentos' be serialized by the method value and be ignored in deserialization. I tried #JsonIgnoreProperty, #JsonGetter and #JsonProperty(access = JsonProperty.Access.READ_ONLY) on the method but nothing works.
You can use JsonIgnoreProperties annotation:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.ThreadLocalRandom;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
Configuracao c = new Configuracao();
c.setDepartamento("D1");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(c);
System.out.println(json);
System.out.println(mapper.readValue(json, Configuracao.class));
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
class Configuracao {
private String departamento;
public String getDepartamento() {
return departamento;
}
public void setDepartamento(String departamento) {
this.departamento = departamento;
}
public String getDepartamentos() {
return departamento + " " + ThreadLocalRandom.current().nextDouble();
}
#Override
public String toString() {
return "Configuracao{" +
"departamento='" + departamento + '\'' +
'}';
}
}
Above code prints:
{"departamento":"D1","departamentos":"D1 0.8600092703789755"}
Configuracao{departamento='D1'}
JsonProperty.Access.READ_ONLY should also works:
class Configuracao {
private String departamento;
public String getDepartamento() {
return departamento;
}
public void setDepartamento(String departamento) {
this.departamento = departamento;
}
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getDepartamentos() {
return departamento + " " + ThreadLocalRandom.current().nextDouble();
}
#Override
public String toString() {
return "Configuracao{" +
"departamento='" + departamento + '\'' +
'}';
}
}
with above test works as expected.
If you have more classes like this and fields to ignore, you can disable globally feature DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Everything was tested with version 2.9.9
Just define departamentos property in Configuracao class.
public class Configuracao{
private String departamento;
private String departamentos;
//omitted getter/setter
}

Jackson - parse different model under same key at runtime

I have a specific json response from server, where under a key the content would be of different models also at a time only one of the model data would be present under the key.
While parsing the response into POJO how can I specify object type at runtime based on other field of contentType on same model.
Following is the code for better understanding of scenario.
Here content_type is type A and so under "content" key there would be model for object of class TypeA
"scheduled_content": {
"some_field": "value",
"content_type": "typeA",
"content" : {
"some_field" : "value"
"more_feilds" : "value"
}
}
Here content_type is type B and so under "content" key there would be model for object of class TypeB
"scheduled_content": {
"some_field": "value",
"content_type": "typeB",
"content" : {
"some_field_b" : "value"
"more_fields_for_b" : "value"
}
}
How can I write POJO classes to parse such json response?
The type classes are completely different models they don't have any field in common.
I believe that what you are looking for is called, in Jackson JSON terms, polymorphic deserialization by property name.
Here is how I do it with Jackson 2.1.4:
First create an abstract class ScheduledContent with common members and an abstract method that would operate on the content. Use the JsonTypeInfo annotation to mark the JSON property that would resolve the specific implementation and the JsonSubTypes annotation to register the subtypes by the values of the property previously specified:
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "content_type")
#JsonSubTypes({
#JsonSubTypes.Type(name = "typeA", value = ScheduledAContent.class),
#JsonSubTypes.Type(name = "typeB", value = ScheduledBContent.class)
})
public abstract class ScheduledContent {
private String someField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public abstract void doSomethingWithContent();
}
The subtypes registration can also be done on the ObjectMapper as you will see later.
Then add the specific implementation for the ScheduledAContent class:
public class ScheduledAContent extends ScheduledContent {
private TypeAContent content;
public void setContent(TypeAContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeAContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeAContent {
private String someField;
private String anotherField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
and also for the ScheduledBContent class:
public class ScheduledBContent extends ScheduledContent {
private TypeBContent content;
public void setContent(TypeBContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeBContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeBContent {
private String someField;
private String anotherField;
#JsonSetter("some_field_b")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field_b")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
And a simple Test class:
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
public class Test {
public static void main(String[] args) {
String jsonA = "{" +
"\"some_field\": \"main_some_field1\"," +
"\"content_type\": \"typeA\"," +
"\"content\" : {" +
" \"some_field\" : \"content_some_field\"," +
" \"another_field\" : \"content_another_field\"" +
"}}";
String jsonB = "{" +
"\"some_field\": \"main_some_field2\"," +
"\"content_type\": \"typeB\"," +
"\"content\" : {" +
" \"some_field_b\" : \"content_some_field_b\"," +
" \"another_field_b\" : \"content_another_field_b\"" +
"}}";
ObjectMapper mapper = new ObjectMapper();
/*
* This is another way to register the subTypes if you want to do it dynamically without the use of the
* JsonSubTypes annotation in the ScheduledContent class
*/
// mapper.registerSubtypes(new NamedType(ScheduledAContent.class, "typeA"));
// mapper.registerSubtypes(new NamedType(ScheduledBContent.class, "typeB"));
try {
ScheduledContent scheduledAContent = mapper.readValue(jsonA, ScheduledContent.class);
scheduledAContent.doSomethingWithContent();
ScheduledContent scheduledBContent = mapper.readValue(jsonB, ScheduledContent.class);
scheduledBContent.doSomethingWithContent();
} catch (IOException e) {
e.printStackTrace();
}
}
}
that will produce the output:
someField: content_some_field
anotherField: content_another_field
someField: content_some_field_b
anotherField: content_another_field_b
Using #JsonSetter in the setter methods may help. But in this case you will need to create setter methods for each type of fields in "content".
#JsonSetter("some_field")
public void setSomeField1(String field1) {
this.field1 = field1;
}
#JsonSetter("some_field_b")
public void setSomeField2(String field2) {
this.field1 = field1;
}

How can I unwrap a specific field in a JSON using Jackson?

I have a JSON payload that looks like this:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"primary_image": {
"id": 247,
"zoom_url": "www.site.com/in_123__14581.1393831046.1280.1280.jpg",
"thumbnail_url": "www.site.com/in_123__14581.1393831046.220.290.jpg",
"standard_url": "www.site.com/in_123__14581.1393831046.386.513.jpg",
"tiny_url": "www.site.com/in_123__14581.1393831046.44.58.jpg"
}
}
Can I unwrap a specific field and discard all the others? In other words, can I bind this directly to a POJO like this:
public class Product {
private Integer id;
private String name;
private String standardUrl;
}
There are lots of ways. Do you need to deserialize, serialize or both?
One way to deserialize would be to use a creator method that takes the image as a tree node:
public static class Product {
private Integer id;
private String name;
private String standardUrl;
public Product(#JsonProperty("id") Integer id,
#JsonProperty("name") String name,
#JsonProperty("primary_image") JsonNode primaryImage) {
this.id = id;
this.name = name;
this.standardUrl = primaryImage.path("standard_url").asText();
}
}
The creator doesn't have to be a constructor, you could have a static method that is only used for Jackson deserialization.
You'd have to define a custom serializer to reserialize this, though (e.g. a StdDelegatingSerializer and a converter to wrap the string back up as an ObjectNode)
There are different ways to skin this cat, I hope you can use Jackson 2 for this, since it offers great ways to deserialize Json data, one of my favorites deserialization features is the one I'll show you here (using Builder Pattern) because allows you to validate instances when they are being constructed (or make them immutable!). For you this would look like this:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.Map;
#JsonDeserialize(builder = Product.Builder.class)
public class Product {
private Integer id;
private String name;
private String standardUrl;
private Product(Builder builder) {
//Here you can make validations for your new instance.
this.id = builder.id;
this.name = builder.name;
//Here you have access to the primaryImage map in case you want to add new properties later.
this.standardUrl = builder.primaryImage.get("standard_url");
}
#Override
public String toString() {
return String.format("id [%d], name [%s], standardUrl [%s].", id, name, standardUrl);
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Builder {
private Integer id;
private String name;
private Map<String, String> primaryImage;
public Builder withId(Integer id) {
this.id = id;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
#JsonProperty("primary_image")
public Builder withPrimaryImage(Map<String, String> primaryImage) {
this.primaryImage = primaryImage;
return this;
}
public Product build() {
return new Product(this);
}
}
}
To test it I created this class:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
String serialized = "{" +
" \"id\": 32," +
" \"name\": \"[Sample] Tomorrow is today, Red printed scarf\"," +
" \"primary_image\": {" +
" \"id\": 247," +
" \"zoom_url\": \"www.site.com/in_123__14581.1393831046.1280.1280.jpg\"," +
" \"thumbnail_url\": \"www.site.com/in_123__14581.1393831046.220.290.jpg\"," +
" \"standard_url\": \"www.site.com/in_123__14581.1393831046.386.513.jpg\"," +
" \"tiny_url\": \"www.site.com/in_123__14581.1393831046.44.58.jpg\"" +
" }" +
" }";
ObjectMapper objectMapper = new ObjectMapper();
try {
Product deserialized = objectMapper.readValue(serialized, Product.class);
System.out.print(deserialized.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
The output is (using the override toString() method in Product:
id [32], name [[Sample] Tomorrow is today, Red printed scarf], standardUrl [www.site.com/in_123__14581.1393831046.386.513.jpg].
There are two ways to get the response you required. For both methods, we are going to use JsonView.
Create two types of JsonView:
public interface JViews {
public static class Public { }
public static class Product extends Public { }
}
First method
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonIgnore
private Image primaryImage;
#JsonView(JViews.Product.class)
public String getStandardUrl{
return this.primaryImage.getStandardUrl();
}
}
Second way
Using Jackson's #JsonView and #JsonUnwrapped together.
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonUnwrapped
private Image primaryImage;
}
public class Image {
private String zoomUrl;
#JsonView(JViews.Product.class)
private String standardUrl;
}
#JsonUnwrapped annotation flattens your nested object into Product object. And JsonView is used to filter accessible fields. In this case, only standardUrl field is accessible for Product view, and the result is expected to be:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"standard_url": "url"
}
If you flatten your nested object without using Views, the result will look like:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"id":1,
"standard_url": "url",
"zoom_url":"",
...
}
Jackson provided #JsonUnwrapped annotation.
See below link:
http://jackson.codehaus.org/1.9.9/javadoc/org/codehaus/jackson/annotate/JsonUnwrapped.html

JSON parsing through GSON returns null values

I am trying to read the values of a JSON output.
This is the JSON output:
{"nameOfSummoner":{"id":56529189,"name":"test","profileIconId":550,"summonerLevel":30,"revisionDate":1422110739000}}
And with the following code I am trying to read it:
final Connector connector = new Connector();
String response = connector.connect("link"); // (Returns a String value of the JSON)
final Gson gson = new Gson();
final Summoner summoner = gson.fromJson(response, Summoner.class); //Summoner is a model class
System.out.println(summoner);
Summoner class:
public class Summoner {
private String name;
private long profileIconId;
private long summonerLevel;
private long revisionDate;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public long getProfileIconId() {
return profileIconId;
}
public void setProfileIconId(final long profileIconId) {
this.profileIconId = profileIconId;
}
public long getSummonerLevel() {
return summonerLevel;
}
public void setSummonerLevel(final long summonerLevel) {
this.summonerLevel = summonerLevel;
}
public long getRevisionDate() {
return revisionDate;
}
public void setRevisionDate
(long revisionDate) {
this.revisionDate = revisionDate;
}
#Override
public String toString() {
return "Summoner{" +
"name='" + name + '\'' +
", profileIconId=" + profileIconId +
", summonerLevel=" + summonerLevel +
", revisionDate=" + revisionDate +
'}';
}
}
And I get the following output on the console:
Summoner{name='null', profileIconId=0, summonerLevel=0, revisionDate=0}
I have sadly no idea why this happens. Any help I get is appreciated. I am fairly sure it has to do with the JSON output that "nameOfSummoner" is on top and maybe that's why it does not read what is below.
As mentioned by #PeterMmm , your input is a map with 1 key-value pair.
You need to Create another POJO with Summoner object as attribute:
public class Sample {
private Summoner nameOfSummoner;
//getters and setters
}
and then try parsing. Or, you could create a Map and parse.
Map<String, Summoner> responseObj = new HashMap<String, Summoner>();
responseObj= gson.fromJson(response, responseObj.class);
Summoner obj = responseObj.get("nameOfSummoner");
You will also need to have "id" attribute in Summoner class I believe, else gson will throw an exception.

Jackson Array Elements to POJO

I'm trying to deserialize an existing JSON document using Jackson, and was wondering if it was possible to perform the following translation without resorting to a stack of custom deserialization.
The input JSON looks like this:
{
"type": "foo",
"content": ["a", "b", {"some": "object", "goes": "here"}, 4]
}
The first 3 elements in content don't change, and are always String, String, SomeDataStructure, Integer (optional)
And I would like to deserialize into something like this:
class Foo {
public static class FooContent {
String some;
String goes;
}
String aString;
String bString;
FooContent content;
Integer cInt;
}
Now I've already come across BeanAsArrayDeserializer, which sounds like it might be what I want, but I can't seem to find anything even remotely like a piece of example code to get me started.
So, any ideas?
You can always implement your custom deserializer. See below source code:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
;
public class JacksonTest {
public static void main(String[] args) throws Exception {
String json = "{\"type\": \"foo\",\"content\": [\"a\", \"b\", {\"some\": \"object\", \"goes\": \"here\"}, 4]}";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true);
Root pojo = mapper.readValue(json, Root.class);
System.out.println(pojo);
}
}
class Root {
public String type;
#JsonDeserialize(using = FooDeserializer.class)
public Foo content;
#Override
public String toString() {
return content.toString();
}
}
class Foo {
public static class FooContent {
public String some;
public String goes;
#Override
public String toString() {
return "{" + some + ", " + goes + "}";
}
}
String aString;
String bString;
FooContent content;
Integer cInt;
#Override
public String toString() {
return "Foo [aString=" + aString + ", bString=" + bString + ", content=" + content
+ ", cInt=" + cInt + "]";
}
}
class FooDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
Foo f = new Foo();
int index = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
JsonToken token = jp.getCurrentToken();
if (token.isStructStart()) {
f.content = jp.readValueAs(Foo.FooContent.class);
index++;
} else {
switch (index++) {
case 0:
f.aString = jp.getText();
break;
case 1:
f.bString = jp.getText();
break;
case 3:
f.cInt = Integer.parseInt(jp.getText());
break;
}
}
}
return f;
}
}
Above app prints:
Foo [aString=a, bString=b, content={object, here}, cInt=4]
The feature that allows to use the BeanAsArrayDeserializer (internally) is #JsonFormat. You can read some information about it in this blog post.
I also just learned about it recently, but I think a mapping that works for your case would look something like the following. You'd need a wrapper to hold the content field though, which would then be of type Foo.
#JsonFormat(shape = Shape.ARRAY)
#JsonPropertyOrder({ "aString", "bString", "content", "cInt" })
class Foo {
public static class FooContent {
String some;
String goes;
}
#JsonFormat(shape = Shape.STRING)
String aString;
#JsonFormat(shape = Shape.STRING)
String bString;
FooContent content;
#JsonFormat(shape = Shape.NUMBER)
Integer cInt;
}

Categories