I have this JSON object:
[
{
"field1": "xxxxx",
"field2": "vvvvvv",
"field3": "cccccc",
"field4": "zzzzzzz"
},
{
"field1": "aaaaa",
"field2": "ssssss",
"field3": "dddddd",
"field4": "ffffff"
}
]
I'm using FasterXML's Jackson library to deserialize this JSON to my class Foo. This one has this structure:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Foo {
private String id;
#JsonProperty("field1")
private String customField1;
#JsonProperty("field2")
private String customField2;
#JsonProperty("field3")
private String customField3;
#JsonProperty("field4")
private String customField4;
................
}
I would like to calculate value of field id at deserialize time. This value is the result of concatenating customField4 with customField3. Is possible to perform this kind of operation or do I need to pass this value into my JSON?
Ok guys, solution is to set a custom
#JsonDeserialize(using = EntityJsonDeserializerCustom.class)
in this way I've created a generic static class with only fields returned by json an then I override deserialize method to return me my object with calculated field
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonDeserialize(using = EntityJsonDeserializerCustom.class)
public class Foo {
private String id;
#JsonProperty("field1")
private String customField1;
#JsonProperty("field2")
private String customField2;
#JsonProperty("field3")
private String customField3;
#JsonProperty("field4")
private String customField4;
................
}
public class EntityJsonDeserializerCustom extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
InnerFoo innerFoo = jp.readValueAs(InnerFoo.class);
Foo foo = new Foo();
foo.setField1(innerFoo.field1);
foo.setField2(innerFoo.field2);
foo.setField3(innerFoo.field3);
foo.setField4(innerFoo.field4);
foo.setId(innerFoo.field4 + innerFoo.field3);
return foo;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class InnerFoo {
#JsonProperty("field1")
private String customField1;
#JsonProperty("field2")
private String customField2;
#JsonProperty("field3")
private String customField3;
#JsonProperty("field3")
private String customField4;
}
}
In this way I solve my problem, I hope this is helpfully for community :D
Related
I would like to know is there a way (probably using deserialiser) in Jackson to copy one attribute value to another object attribute the container has.
For example, documentId from the TestData class also needs to be persisted in the Details.documentId attribute.
json/TestData.json
{
"id": "1",
"documentId" : "234234",
"details" : {
"name": "test",
"lastName": "asdf"
}
}
#RequiredArgsConstructor(onConstructor_ = #Autowired)
#SpringBootTest(classes = ExampleMicroserviceApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
#ActiveProfiles("test")
public class TestDocumentId {
final ObjectMapper objectMapper;
#Value("classpath:json/TestData.json")
Resource testData;
#SneakyThrows
#Test
void testDocumentIdPresentInDetails() {
var data = objectMapper.readValue(testData.getFile(), TestData.class);
assertThat(data.documentId).isNotNull();
assertThat(data.getDetails().name).isNotNull();
assertThat(data.getDetails().documentId).isNotNull();
}
#Data
public static class TestData {
private String id;
private String documentId;
private Details details;
#Data
public static class Details {
private String documentId;
private String name;
private String lastName;
}
}
}
At the deserialization process (which as I understand is the process of converting JSON data into a Java Object), how can I tell Jackson that when it reads a object that contains no data, it should be ignored?
I'm using Jackson 2.6.6 and Spring 4.2.6
The JSON data received by my controller is as follows:
{
"id": 2,
"description": "A description",
"containedObject": {}
}
The problem is that the object "containedObject" is interpreted as is and it's being instantiated. Therefore, as soon as my controller reads this JSON data, it produces an instance of the ContainedObject object type but I need this to be null instead.
The easiest and fastest solution would be that in the JSON data received, this value be null like this:
{
"id": 2,
"description": "A description",
"containedObject": null
}
But this isn't possible since I'm not in control of the JSON data that is sent to me.
Is there an annotation (like this explained here) that works for the deserialization process and could be helpfull in my situation?
I leave a representation of my classes for more information:
My entity class is as follows:
public class Entity {
private long id;
private String description;
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
And my contained object class as follows:
public class ContainedObject {
private long contObjId;
private String aString;
//Contructor, getters and setters omitted
}
I would use a JsonDeserializer. Inspect the field in question, determine, if it is emtpy and return null, so your ContainedObject would be null.
Something like this (semi-pseudo):
public class MyDes extends JsonDeserializer<ContainedObject> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
//read the JsonNode and determine if it is empty JSON object
//and if so return null
if (node is empty....) {
return null;
}
return node;
}
}
then in your model:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = MyDes.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
Hope this helps!
You can implement a custom deserializer as follows:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = EmptyToNullObject.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
public class EmptyToNullObject extends JsonDeserializer<ContainedObject> {
public ContainedObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
long contObjId = (Long) ((LongNode) node.get("contObjId")).numberValue();
String aString = node.get("aString").asText();
if(aString.equals("") && contObjId == 0L) {
return null;
} else {
return new ContainedObject(contObjId, aString);
}
}
}
Approach 1 : This is mostly used. #JsonInclude is used to exclude properties with empty/null/default values.Use #JsonInclude(JsonInclude.Include.NON_NULL) or #JsonInclude(JsonInclude.Include.NON_EMPTY) as per your requirement.
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
private String empId;
private String firstName;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String lastName;
private String address;
private String emailId;
}
More info about the jackson annotations : https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations
Approach 2 : GSON
use GSON (https://code.google.com/p/google-gson/)
I have the following Json
{
"coreId" : "1",
"name" : "name",
"additionalValueList" : [
{
"columnName" : "allow_duplicate",
"rowId" : "10",
"value" : "1"
},
{
"columnName" : "include_in_display",
"rowId" : "11",
"value" : "0"
},
...e.t.c
]
},
...e.t.c
and Java class
class DTO {
#JsonProperty("coreId")
private Integer id;
private String name;
private Boolean allowDuplicate;
private Boolean includeInDisplay;
}
How I can easily map values from 'additionalValueList' to corresponding java fields.For example Json value from field 'columnName' - 'allow_duplicate' = DTO.allowDuplicate.
Actually I know how to do it with custom deserializers with #JsonDeserialize annotation and smth like this.Bu I have 40+ DTO and it is not a good idea to create own deserializer for each filed. I am looking for solution to have for example 1 deserializer(since values structure in 'additionalValueList' are the same for all entities) and to pass parameter(field name that I want to map to that field) to custom deserializer that will find in 'additionalValueList' entity with 'column Name' = parameter(that I passed from annotation) and return 'value'.
Example
class DTO {
#JsonProperty("coreId")
private Integer id;
private String name;
#JsonDeserialize(using = MyCustDeser.class,param = allow_duplicate)
private Boolean allowDuplicate;
#JsonDeserialize(using = MyCustDeser.class,param = include_in_display)
private Boolean includeInDisplay;
}
It will be a good solution but maybe not easy to achieve.However I will be very grateful for all your advices.Thank you.
Create a Converter class, then specify it on the DTO class.
The following code uses public fields for the simplicity of the example.
/**
* Intermediate object used for deserializing FooDto from JSON.
*/
public final class FooJson {
/**
* Converter used when deserializing FooDto from JSON.
*/
public static final class ToDtoConverter extends StdConverter<FooJson, FooDto> {
#Override
public FooDto convert(FooJson json) {
FooDto dto = new FooDto();
dto.name = json.name;
dto.id = json.coreId;
dto.allowDuplicate = lookupBoolean(json, "allow_duplicate");
dto.includeInDisplay = lookupBoolean(json, "include_in_display");
return dto;
}
private static Boolean lookupBoolean(FooJson json, String columnName) {
String value = lookup(json, columnName);
return (value == null ? null : (Boolean) ! value.equals("0"));
}
private static String lookup(FooJson json, String columnName) {
if (json.additionalValueList != null)
for (FooJson.Additional additional : json.additionalValueList)
if (columnName.equals(additional.columnName))
return additional.value;
return null;
}
}
public static final class Additional {
public String columnName;
public String rowId;
public String value;
}
public Integer coreId;
public String name;
public List<Additional> additionalValueList;
}
You now simply annotate the DTO to use it:
#JsonDeserialize(converter = FooJson.ToDtoConverter.class)
public final class FooDto {
public Integer id;
public String name;
public Boolean allowDuplicate;
public Boolean includeInDisplay;
#Override
public String toString() {
return "FooDto[id=" + this.id +
", name=" + this.name +
", allowDuplicate=" + this.allowDuplicate +
", includeInDisplay=" + this.includeInDisplay + "]";
}
}
Test
ObjectMapper mapper = new ObjectMapper();
FooDto foo = mapper.readValue(new File("test.json"), FooDto.class);
System.out.println(foo);
Output
FooDto[id=1, name=name, allowDuplicate=true, includeInDisplay=false]
I'm trying to parse JSON like the following into object using Jackson on Android (Note: I'm not in control of the JSON format - the format comes from Yammer)
"references": [
{
"type": "user",
"id": 12345678,
"name": "Wex"
},
{
"type": "message",
"id": 12345679,
"body":
{
"plain":"A short message"
}
},
{
"type": "thread",
"id": 12345670,
"thread_starter_id": 428181699
}
]
The problem is that each entry in references is a different type of object with different properties. As a start I've got:
public static class Reference
{
public String type;
public String id;
}
I'd rather avoid putting all potential properties in one object like:
public static class Reference
{
public static class Body
{
public String plain;
}
public String type;
public String id;
public String name;
public Body body;
public String thread_starter_id;
}
And want to use separate classes that are created dependant on the type value like:
public static class ReferenceUser extends Reference
{
public String name;
}
public static class ReferenceMessage extends Reference
{
public static class Body
{
public String plain;
}
public Body body;
}
public static class ReferenceThread extends Reference
{
public String thread_starter_id;
}
So... what's the best way of getting Jackson to parse the JSON like that?
I'm currently parsing it quite simply like this:
ObjectMapper mapper = new ObjectMapper();
Reference[] references = mapper.readValue(json, Reference[].class);
you can do something like this with Jackson:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(name = "user", value = ReferenceUser.class),
#JsonSubTypes.Type(name = "message", value = ReferenceMessage.class),
#JsonSubTypes.Type(name = "thread", value = ReferenceThread.class)
})
public class Reference {
int id;
String name;
}
This way you will generate subclasses.
John
I want to deserialize the following JSON object:
{
"id":"001",
"module_name":"Users",
"name_value_list":
{
"user_name": {"name":"user_name", "value":"admin"},
"full_name": {"name":"full_name", "value":"LluĂs Pi"},
"city": {"name":"full_name", "value":"Barcelona"},
"postal_code": {"name":"postal_code", "value":"08017"},
...
}
}
into some Java object like this:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
public class UserEntry
{
private String id;
private String moduleName;
private Person nameValueList;
public String getId()
{
return id;
}
public String getModuleName()
{
return moduleName;
}
public Person getPerson()
{
return nameValueList;
}
}
where Person is the following class:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
class Person
{
private String userName;
private String fullName;
private String city;
private String postalCode;
}
using Jackson but I get a deserialization error.
If I change the type of field nameValueList to a Map all the deserialization process goes with no problem and I get a map where the key is the "name" value and the value is the "value" value.
So my question is: is there any simple, or no so simple, way to deserialize this kind of JSON object to a Java Pojo with properties prop_1, prop_2, prop_3and prop_4?
{
"name_value_list":
{
"prop_1": {"name":"prop_1", "value":"value_1"},
"prop_2": {"name":"prop_2", "value":"value_2"},
"prop_3": {"name":"prop_3", "value":"value_3"},
"prop_4": {"name":"prop_4", "value":"value_4"},
...
}
}
Not very simple and not very clean. However you can do it by implementing a any setter field for the JSON attributes in the Person class which don't match any attribute on your UserEntry POJO.
#JsonAnySetter
public void putUserField(String userKey, Map<String, String> userValue)
throws NoSuchFieldException {
String actualFieldName = getActualFieldName(userKey);
Field field = this.getClass().getDeclaredField(actualFieldName);
field.setAccessible(true);
ReflectionUtils.setField(field, this, userValue.get("value"));
}
private String getActualFieldName(String userKey) {
return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, userKey);
}
In addition to that, I had to change the Jackson attributes for the Person class to
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY,
getterVisibility = JsonAutoDetect.Visibility.NONE)
for it to work for attributes like "city" which don't need any name transformation because jackson tries to directly set the field which fails.