Jackson enum deserialization - java

I'm trying to deserialize json object using jackson and getting exception
`com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "mobile" (class mypack.JacksonPhoneBuilder), not
marked as ignorable (2 known properties: , "phoneType", "value"]) at
[Source: %filelocation%; line: 8, column: 24] (through reference
chain:
mypack.JacksonAddressListBuilder["addresses"]->mypack.JacksonAddressBuilder["phones"]->mypack.JacksonPhoneBuilder["mobile"])`
This is the object:
{
"addresses": [
{
...
"phones": {
"mobile": "+01234567890"
}
},
...
]
}
Phone.java:
#JsonDeserialize(builder = JacksonBuilder.class)
public class Phone {
protected String value;
protected Type type;
// setters and getters
}
i've read about jackson enum deserializtion, but there was plain enum
and there was used Map. Obviously, field "mobile" is not
represented in model, but it's a enum value, so how can i deserialize
it?

Your JacksonPhoneBuilder works the same way as Jackson default deserialization. The problem is that it's able to read phones in following form:
{
"type": "mobile",
"value": "+01234130000"
}
However in your json object phones are represented as a subobject which can be seen in Java as a Map<PhoneType, String>. One of possible solutions is to use a Converter from Map to List (I assume there may be many phones in one address).
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.Converter;
public class PhoneConverter implements Converter<Map<PhoneType, String>, List<Phone>>{
public List<Phone> convert(Map<PhoneType, String> phonesMap) {
List<Phone> phones = new ArrayList<Phone>();
for (PhoneType phoneType : phonesMap.keySet()) {
phones.add(new Phone(phoneType, phonesMap.get(phoneType)));
}
return phones;
}
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructMapLikeType(Map.class, PhoneType.class, String.class);
}
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructCollectionLikeType(List.class, Phone.class);
}
}
Then in your Address class:
public class Address {
#JsonDeserialize(converter = PhoneConverter.class)
protected List<Phone> phones;
}
Note that it won't play with your Builders but if you don't do any other custom deserialization then you don't need them - you can rely on Jackson's default behavior.

Related

Jackson use the first non-null alias

I'm using Jackson to parse JSON data on a Spring Boot application, but Jackson is unable to select properly the JSON fields to map to the POJO fields.
Details
Firstly, I'm reading data from a third party library, and their data model has a lot of redundancy (namely the several properties can represent the same information), and I can't ask them to fix it.
Their JSON is something like that:
{ "name" : "name", "full_name" : "name", "fullName" : "name"}
there are 3 properties in JSON containing the same information. But sometimes there would be only one of these properties which is non-null, like:
{ "name" : null, "full_name" : "", "fullName" : "name"}
And that happens to many other properties.
I've tried to use #JsonAlias to extract the required (non-null) data from the incoming JSON, but it doesn't resolve the issue.
#JsonProperty("name")
#JsonAlias({"full_name","fullName"})
private String name;
First, that #JsonAlias is taking precedence from the #JsonProperty value. Second, it's not ignoring null values.
How can I make Jackson ignore null values in a situation like described above?
Multiple Setters
One of the possible options is to define a bunch of additional setters for each duplicated property, annotated with #JsonSetter to map each setter to a corresponding property flavor.
To avoid logic duplication, every additional setter should delegate to a regular setter, which should determine if the existing value needs to be updated (if it's empty, null, etc.).
public class MyPojo {
public static final Predicate<String> NULL_OR_EMPTY =
s -> s == null || s.isEmpty(); // predicate can be reused to check multiple properties
private String name;
#JsonSetter("name")
public void setName(String name) {
if (NULL_OR_EMPTY.test(this.name)) this.name = name;
}
#JsonSetter("full_name")
public void setName1(String name) {
setName(name);
}
#JsonSetter("fullName")
public void setName2(String name) {
setName(name);
}
}
Usage example:
String json = "{ "name" : null, "full_name" : "", "fullName" : "name"}";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json, MyPojo.class);
System.out.println(myPojo);
Output:
MyPojo{name='name'}
The drawbacks of this approach are the usage of "smart-setters", which might not considered to be clean (because it violates the Single responsibility principle, setters aren't meant to perform validation) and domain class gets polluted with additional setter methods. Both issues can be solved by externalizing this functionality.
Custom Converter
Another possible solution is to customize deserialization by defining a Converter for the target type.
Note: don't confuse Converter and Deserializer. Deserializer is meant to provide logic how to construct a POJO based on the information contained in the JsonParser, whilst Converter is used to transform one POJO (which is easier to deserialize) into another POJO.
So we need to create two classes: Converter and auxiliary type reflecting the data model of the incoming JSON.
Consider the following auxiliary POJO:
public record AuxiliaryPojo(
#JsonProperty("name") String name,
#JsonProperty("full_name") String name1,
#JsonProperty("fullName") String name2
) {}
And that's the Converter extending StdConverter that bridges AuxiliaryPojo and MyPojo:
public class AuxiliaryPojoToMyPojo extends StdConverter<AuxiliaryPojo, MyPojo> {
public static final Predicate<String> NOT_NULL_OR_EMPTY =
s -> s != null && !s.isEmpty();
#Override
public MyPojo convert(AuxiliaryPojo v) {
return MyPojo.builder()
.name(findMatching(v.name(), v.name1(), v.name2()))
.build();
}
private String findMatching(String... args) {
return Arrays.stream(args)
.filter(NOT_NULL_OR_EMPTY)
.findFirst().orElse(null);
}
}
And here's the domain class (free from any redundant code). Note that Converter has been specified via converter property of the #JsonDeserialize annotation.
#Getter
#Builder
#JsonDeserialize(converter = AuxiliaryPojoToMyPojo.class)
public static class MyPojo {
private String name;
}
That would be enough to parse the sample JSON into an instance of MyPojo:
String json = "{ "name" : null, "full_name" : "", "fullName" : "name"}";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json,bMyPojo.class);
System.out.println(myPojo);
Output:
MyPojo{name='name'}
An other solution is to ignore additionnal fields 'fullName' and 'full_name' using annotation #JsonIgnoreProperties and a custom deserializer that throws an exception when name property is null (or equal to string "null") so that you can catch that exception in order not to create a person when name is null.
See code below:
Class Person
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
#JsonIgnoreProperties({ "fullName", "full_name" })
public class Person {
#JsonProperty("name")
#JsonDeserialize(using = CustomNameDeserializer.class)
private String name;
public Person() {
super();
}
public String toString() {
return "name: " + name;
}
public static void main(String[] args) {
String[] personArrayJson = new String[3];
personArrayJson[0]="{ \"name\" : \"nameNotNull1\", \"full_name\" : \"name\", \"fullName\" : \"name\"}";
personArrayJson[1]="{ \"name\" : \"null\", \"full_name\" : \"\", \"fullName\" : \"name\"}";
personArrayJson[2]="{ \"name\" : \"nameNotNull2\", \"full_name\" : \"name\", \"fullName\" : \"name\"}";
List<Person> persons = new ArrayList<Person>();
ObjectMapper mapper = new ObjectMapper();
Person p;
for (String personJson : personArrayJson) {
try {
p = mapper.readValue(personJson, Person.class);
persons.add(p);
} catch (JsonProcessingException e) {
//e.printStackTrace();
System.out.println("NAme is null: not deserialized");
}
}
System.out.println("Persons list contains "+persons.size()+" persons => "+persons);
}
}
Class CustomNameDeserializer
import java.io.IOException;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
public class CustomNameDeserializer extends StdDeserializer<String> {
public CustomNameDeserializer(Class<String> s) {
super(s);
}
public CustomNameDeserializer() {
this(null);
}
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException, NullPointerException{
String n = p.getValueAsString();
if(n==null || n.equals("null") || n.trim().equals(""))
throw new NullPointerException("Name is null");
return n;
}
}
Hope it helps

Is Jackson Serialization of this string possible without custom serializer?

I want to serialize a JSON-String I receive as a POJO, for further usage in my code, but I am struggling to get it working without writing a custom serializer.
I would prefer as solution without writing a custom serializer, but if that is the only possible way I will write one.
Additionally I believe the data I receive is a weird JSON since the list I request is not sent as list using [] but rather as a object using {}.
I receive the following list/object (shortened):
{
"results": {
"ALL": {
"currencyName": "Albanian Lek",
"currencySymbol": "Lek",
"id": "ALL"
},
"XCD": {
"currencyName": "East Caribbean Dollar",
"currencySymbol": "$",
"id": "XCD"
},
"EUR": {
"currencyName": "Euro",
"currencySymbol": "â?¬",
"id": "EUR"
},
"BBD": {
"currencyName": "Barbadian Dollar",
"currencySymbol": "$",
"id": "BBD"
},
"BTN": {
"currencyName": "Bhutanese Ngultrum",
"id": "BTN"
},
"BND": {
"currencyName": "Brunei Dollar",
"currencySymbol": "$",
"id": "BND"
}
}
}
I created my first POJO for the inner object like this:
public class CurrencyDTO implements Serializable {
private String currencyName;
private String currencySymbol;
private String currencyId;
#JsonCreator
public CurrencyDTO( #JsonProperty( "currencyName" ) String currencyName, #JsonProperty( "currencySymbol" ) String currencySymbol,
#JsonProperty( "id" ) String currencyId )
{
this.currencyId = currencyId;
this.currencyName = currencyName;
this.currencySymbol = currencySymbol;
}
}
which itself is fine. Now I wrote another POJO as a wrapper for the data a layer above which looks like this:
public class CurrencyListDTO implements Serializable {
private List<Map<String, CurrencyDTO>> results;
public CurrencyListDTO()
{
}
}
Adding the annotations #JsonAnySetter or using the #JsonCreator didn't help either, so I removed them again and now I am wondering which little trick could enable the correct serialization of the json.
My Exception is the following:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token
at [Source: (String)"{"results":{"ALL":{"currencyName":"Albanian Lek","currencySymbol":"Lek","id":"ALL"},"XCD":{"currencyName":"East Caribbean Dollar","currencySymbol":"$","id":"XCD"},"EUR":{"currencyName":"Euro","currencySymbol":"â?¬","id":"EUR"},"BBD":{"currencyName":"Barbadian Dollar","currencySymbol":"$","id":"BBD"},"BTN":{"currencyName":"Bhutanese Ngultrum","id":"BTN"},"BND":{"currencyName":"Brunei Dollar","currencySymbol":"$","id":"BND"},"XAF":{"currencyName":"Central African CFA Franc","id":"XAF"},"CUP":{"cur"[truncated 10515 chars]; line: 1, column: 12] (through reference chain: com.nico.Banking.api.data.dto.CurrencyListDTO["results"])
You should change your CurrencyListDTO to:
public class CurrencyListDTO {
private Map<String, CurrencyDTO> results;
// getters and setters
}
Because the results field in the response object is another object with the currencyId as key and no array.
You then can create your list of currencies like this:
ObjectMapper mapper = new ObjectMapper();
CurrencyListDTO result = mapper.readValue(json, CurrencyListDTO.class);
List<CurrencyDTO> currencies = new ArrayList<>(result.getResults().values());
Your CurrencyListDTO should look like below. results property is a JSON Object which should be mapped directly to Map. You can convert it to Collection using keySet or values methods.
class CurrencyListDTO implements Serializable {
private Map<String, CurrencyDTO> results;
public Map<String, CurrencyDTO> getResults() {
return results;
}
public void setResults(Map<String, CurrencyDTO> results) {
this.results = results;
}
#Override
public String toString() {
return "CurrencyListDTO{" +
"results=" + results +
'}';
}
}

Embedding a Jackson JsonNode in POJO stored in CrudRepository

Here's where I'm at. I've an MVC controller method that accepts JSON content. Because I need to validate it using JSON Schema, my controller maps the request body as a Jackson JsonNode.
Upon successful validation, I need to persist the data in Spring Couchbase repository. Consider the following snippet:
public class Foo
{
#Id
private String _id;
#Version
private Long _rev;
#Field
private JsonNode nodeData;
// .. Other data and members.
}
//
// Repository
//
#Repository
public interface FooRepository extends CrudRepository<Foo, String> {
}
When I store these elements into the Couch repository, what I'd like to see is something like this:
{
"_class": "Foo",
"field1": "field 1 data",
"nodeData" : {
"Some" : "additional data",
"from" : "JsonNode"
}
}
instead, what I see in the repository is something like this:
{
"_class": "Foo",
"field1": "field 1 data",
"nodeData" : {
"_children": {
"Some": {
"_value": "additional data",
"_class": "com.fasterxml.jackson.databind.node.TextNode"
},
"From": {
"_value": "jsonNode",
"_class": "com.fasterxml.jackson.databind.node.TextNode"
},
"_nodeFactory": {
"_cfgBigDecimalExact": false
}
}
}
Each stored property of the JsonNode is decorated with class information, and other meta-data, which is not desirable.
My question - is there a preferred way to get the CrudRepository to behave in the manner that I wish?
It doesn't work that way because serialization and de-serialization conventions are already established. You can override these conventions with custom serialization & de-serialization in Jackson-- but that might go beyond the "crude" approach you are looking for.
I see you want a one shoe fits all approach to data modeling.
Might i recommend storing a Map
#Field
private Map<String, String> data;
This map is private so its perfect.
You can then have two methods
one method puts to the map like so
ObjectMapper mapper = new ObjectMapper()
public void setFeild(String name, Object value) {
ObjectNode node new ObjectNode(JsonNodeFactory.instance);
node.put("clazz", value.getClass().getName());
if (value instance of String) {
node.put("value", value)
} else {
node.put("value", mapper.writeValueAsString(data));
}
data.put(name, node.toString());
}
the other gets from the map like so
public Object getField(String name) {
if (data.contains(name)) {
JsonNode node = mapper.readValue(data.get(name), JsonNode.class);
Class clazz = Class.forName(node.get("class").textValue());
if (clazz.equals(String.class) {
return node.get("value").textValue();
} else {
return (Object) mapper.readValue(node.get("value"), clazz);
}
}
}
You should update this implementation to handle Date, Integer, Boolean, Double ... etc the same way i am handling String-- POJOs are what you serialize/de-serialize to/from json.
I hope this makes sense.

Jackson incorrectly deserializing nested object's values into parent

I have the following JSON file which I am trying to deserialize:
{
"name": "ComponentX",
"implements": ["Temperature.Sensor"],
"requires": [
{"type": "interface", "name": "Temperature.Thermostat", "execute": [
"../Thermostat.exe"
]}
]
}
It is a description of a component in a code sample for a distributed system.
Here is the class that this is supposed to deserialize to:
public class ComponentDescription {
#JsonProperty("name")
public String Name;
#JsonProperty("implements")
public String[] Implements;
#JsonProperty("requires")
public ComponentDependency[] Requires;
#JsonIgnore
public String RabbitConnectionName;
private static final ObjectMapper mapper = new ObjectMapper();
public static ComponentDescription FromJSON(String json)
throws JsonParseException, JsonMappingException, IOException
{
return mapper.readValue(json, ComponentDescription.class);
}
public class ComponentDependency
{
#JsonCreator
public ComponentDependency() {
// Need an explicit default constructor in order to use Jackson.
}
#JsonProperty("type")
public DependencyType Type;
#JsonProperty("name")
public String Name;
/**
* A list of ways to start the required component if it is not already running.
*/
#JsonProperty("execute")
public String[] Execute;
}
/**
* A dependency can either be on "some implementation of an interface" or it
* can be "a specific component", regardless of what other interface implementations
* may be available.
*/
public enum DependencyType
{
Interface,
Component
}
}
When I run ComponentDescription.FromJSON(jsonData), which uses the Jackson ObjectMapper to deserialize the JSON into the appropriate classes, I get the following exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "type" (class edu.umd.cs.seam.dispatch.ComponentDescription), not marked as ignorable (3 known properties: , "implements", "name", "requires"])
at [Source: java.io.StringReader#39346e64; line: 1, column: 103] (through reference chain: edu.umd.cs.seam.dispatch.ComponentDescription["requires"]->edu.umd.cs.seam.dispatch.ComponentDescription["type"])
It seems that Jackson is trying to deserialize the requires array in the JSON object as an array of ComponentDescription instead of an array of ComponentDependency. How do I get it to use the correct class? I would prefer an answer that gets Jackson to look at the type of public ComponentDependency[] Requires and see use it automatically over an answer that requires me to put the type name in again somewhere else (such as an # attribute).
My guess is that the problem comes from ComponentDependency not being static. As it is not declared static, it means it can only be instantiated with an existing instance of ComponentDescription.
For more details, see here.

Mapping Json to POJO

{
"Filters": [{
"Decription": "Default",
"FieldSelected": {
"AppointmentDate": true,
"AppointmentDateOrder": 1
"ptStatusOrder": 3
},
"FilterID": 1
}, {
"Decription": "chart",
"FieldSelected": {
"AppointmentDate": true,
"AppointmentDateOrder": 1,
"ptStatusOrder": 0
},
"FilterID": 2
}]
}
I am getting a response of this structure, how do i map it to my POJO class. Can anyone give a sample POJO class for this json structure. I am using Gson for Request and Response.
Just map one Json Object as a Java Class. And make an Array of Object as a List..
Like, (pseudo code only)
You are using GSON library so import SerializedName in your pojo classes.
import com.google.gson.annotations.SerializedName;
Pojo Class looks like,
public class Filter {
#SerializedName("Decription") // This requires same as your Json key
public String description;
#SerializedName("FieldSelected") // The Json Object of FieldSelected
public Field listDetails;
}
public class Field {
#SerializedName("ptStatusOrder")
public int status;
#SerializedName("AppointmentDateOrder")
public int dateOrder;
#SerializedName("AppointmentDate")
public boolean appDate;
}
And main ListFileter class,
public class ListFilter {
#SerializedName("Filters")
public List<Filter> listFilter;
}
And in Android code,
Gson gson = new Gson();
ListFilter listFilter = gson.fromJson(jsonResponse, ListFilter.class);

Categories

Resources