I have a JSON string that contains a nested and wrapped JSON string. I'd like to deserialize this using Jackson but I'm unsure how. Here's a sample bean:
#JsonIgnoreProperties(ignoreUnknown = true)
public class SomePerson {
public final String ssn;
public final String birthday;
public final Address address;
#JsonCreator
public SomePerson(
#JsonProperty("ssn") String ssn,
#JsonProperty("birthday") String birthday,
#JsonProperty("data") Address address,
#JsonProperty("related") List<String> related) {
this.ssn = ssn;
this.birthday = birthday;
this.address = address;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Address {
public final String city;
public final String country;
#JsonCreator
public Address(
#JsonProperty("city") String city,
#JsonProperty("country") String country) {
this.city = city;
this.country = country;
}
}
}
The JSON string resembles this:
{
ssn: "001",
birthday: "01.01.2020",
address: "{ city: \"London\", country: \"UK\" }"
}
While I've deserialized nsted objects before - I'm rather lost as to how to do this when the object is a wrapped string.
When internal object is escaped JSON String we need to deserialise it "twice". First time is run when root JSON Object is deserialised and second time we need to run manually. To do that we need to implement custom deserialiser which implements com.fasterxml.jackson.databind.deser.ContextualDeserializer interface. It could look like this:
class FromStringJsonDeserializer<T> extends StdDeserializer<T> implements ContextualDeserializer {
/**
* Required by library to instantiate base instance
* */
public FromStringJsonDeserializer() {
super(Object.class);
}
public FromStringJsonDeserializer(JavaType type) {
super(type);
}
#Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
return ((ObjectMapper) p.getCodec()).readValue(value, _valueType);
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
return new FromStringJsonDeserializer<>(property.getType());
}
}
We need to annotate our property with this class:
#JsonDeserialize(using = FromStringJsonDeserializer.class)
public final Address address;
Since now, you should be able to deserialise above JSON payload to your POJO model.
See also:
How to inject dependency into Jackson Custom deserializer
Is it possible to configure Jackson custom deserializers at class level for different data types?
readValue(String,Class) should work:
Address addObject = mapper.readValue(root.getString("address"), Address.class);
Where root is your main JSONObject.
Related
I have a problem during the deserialization of a response. Let's suppose I have this response from third party using webclient .
Response :
{
"name":"FirstName",
"type":"Steel",
"Fee":{
"id":"1234",
"name":"FeeFirstName"
},
"address":"2nd Street"
}
This is how my pojo classes looks like
public class Fee{} //generic OR empty class
public class Foo{
private String name;
private String type;
private Fee fee;
private String address;
}
My webclient get response code :
#Autowired
private WebClient fooWebClient;
public Foo getFoo()
{
try{
return fooWebClient.get()
.uri(uriBuilder -> uriBuilder.path("/foo/fee").build("123"))
.header(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Foo.class)
.block();
}catch(Exception e){throw new ApiClientException(e.getMessage());}
}
The above webclient getFoo() code is not giving me the full response, the Fee is coming blank stating "Class has no fields". Rest of the values are coming properly in response.
Fee needs to be empty as any other object can also come.
Please let me know how to deserialize the whole response.
You don't need the Fee class, you can get rid of it entirely and use a Map instead:
public class Foo {
private String name;
private String type;
private Map<String, Object> fee;
private String address;
}
We cannot dynamically create POJO and hence we are left with two options.
Add necessary fields to the 'Fee' class (If you know Fee structure upfront)
If you are not sure about the 'Fee' structure go for Map.
Because spring integrates Jackson you can create a custom Jackson JSON Deserializer for the Fee class that gives you more control:
#JsonDeserialize(using = FeeDeserializer.class)
public class Fee {
private String id;
private String name;
public Fee(String id, String name) {
this.id = id;
this.name = name;
}
}
import com.fasterxml.jackson.*;
public class FeeDeserializer extends JsonDeserializer<Fee> {
#Override
public Fee deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
JsonNode id = tree.get("id");
JsonNode name = tree.get("name");
return (id != null && name != null) ? new Fee(id.asText(), name.asText()) : null;
}
}
For more details see
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.json.jackson.custom-serializers-and-deserializers
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-codecs-jackson
in Microservice, we post multiple dtos data as string json.
Controller:
#RequestMapping(value="/json",method = RequestMethod.POST)
public String getjson(#RequestBody String json) {
///Service process
}
Post Json:
{
"dtos":{
"Dto1":{
"name":"Dto1 Name Field",
"filter":[
{"key":"f1","value":1},
{"key":"f2","value":10}
]
},
"Dto2":{
"city":"Newyork",
"filter":[
{"key":"f1","value":1},
{"key":"f2","value":10},
{"key":"f3","value":10}
]
}
},
"page":1
}
DTO:
public class Dto1{
private String name;
}
public class Dto2{
private String city;
}
Dto1 and Dto2 is java DTO object name.
how to convert string json to java objects?
You can create a new DTO that contains all attrs and receive in request:
public class Filter{
private String key;
private int value;
}
public class Dto1{
private String name;
private List<Filter> filter;
}
public class Dto2{
private String city;
private List<Filter> filter;
}
public class Dtos{
public Dto1 dto1;
public Dto2 dto2;
}
public class DtoToReceiveInRequest{
private Dtos dtos;
private int page;
}
Controller
#PostMapping
public String getjson(#RequestBody DtoToReceiveInRequest json) {
///Service process
}
You can use the ObjectMapper from the jackson library, like below.
String json = "";
ObjectMapper objectMapper = new ObjectMapper();
Dto1 dto = objectMapper.readValue(json, Dto1.class);
But in your particular example, you don't have to have two DTO classes. You can encapsulate values in one DTO and have the list of different instances of that DTO in a json format.
NB. The json string should be a representation of the preferred class you want to retrieve, eg Dto1.java.
My api response seems like this
{
"name": "jackson",
"age": 33,
"hobby_list": "[{\"name\":\"soccer\", \"priority\":2}, {\"name\":\"game\", \"priority\":1}, {\"name\":\"reading\", \"priority\":3}]"
}
I want to deserialize hobby_list string value as object.
class Person {
#JsonProperty("name")
private String name;
#JsonProperty("age")
private Integer age;
#JsonProperty("hobby_list")
private List<Hobby> hobbyList;
}
class Hobby(
#JsonProperty("name")
private String name;
#JsonProperty("priority")
private Integer priority;
)
It doesn't work as you know.
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<com.joont.domain.Hobby>` out of VALUE_STRING token
What is the best practice to solve the problem?
Annotation? Configure? Custom deserializer?
You can convert them by registering custom deserializer as below:
public class PersonDeserializer extends JsonDeserializer<Person> {
#Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
final Integer age = node.get("age").asInt();
final String name = node.get("name").asText();
final String hobbyListAsString = node.get("hobby_list").asText();
ObjectMapper mapper = new ObjectMapper();
// convert JSON array to List of objects
List<Hobby> hobbyList = Arrays.asList(mapper.readValue(hobbyListAsString, Hobby[].class));
Person person = new Person();
person.setName(name);
person.setAge(age);
person.setHobbyList(hobbyList);
return person;
}
}
and in pojo at root use annotation #JsonDeserialize(using = PersonDeserializer.class) so that above deserializer can be registered. Attaching reference below:
#JsonDeserialize(using = PersonDeserializer.class)
#Data
public class Person {
#JsonProperty("name")
private String name;
#JsonProperty("age")
private Integer age;
#JsonProperty("hobby_list")
private List<Hobby> hobbyList;
}
Then I was able to deserialize above hobby_list string to object
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(content, Person.class);
System.out.println(person.getHobbyList());
I am unable to find the correct way to manipulate the casing of a value via a custom annotation that I can control conditionally.
I have already looked into Jackson tutorials online with JsonFilters, Serialization, Deserialization - I have had most of them working but not with my desired outcome.
Custom Serializer:
public class CaseSerializer extends JsonSerializer<String> {
private boolean myCheck;
public CaseSerializer() {
// default
}
public CaseSerializer(boolean myControl) {
this.myCheck = myControl;
}
#Override
public void serialize(String value, JsonGenerator generator, SerializerProvider provider) throws IOException {
if (value != null) {
if (myCheck) {
generator.writeString(value.toUpperCase());
} else {
generator.writeString(value);
}
}
}
}
Person Model:
public class Person {
#JsonSerialize(using = CaseSerializer.class)
private String title;
private String firstName;
private String lastName;
public Person(String title, String firstName, String lastName) {
this.title = title;
this.firstName = firstName;
this.lastName = lastName;
}
// getters, setters & toString
}
Usage:
Person person = new Person("dr", "john", "doe");
SimpleModule module = new SimpleModule();
module.addSerializer(String.class,new CaseSerializer(true));
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
String titleShouldBeUpper = mapper.writeValueAsString(person);
System.out.println(titleShouldBeUpper);
From running the code provided I expect that the title field within the Person class to be uppercased and the other values to remain as they were.
(CaseSerializer(true) meaning I want to uppercase the values)
This is not the case as the code prints the following output:
{"title":"dr","firstName":"JOHN","lastName":"DOE"}
When I run the code with the CaseSerializer() without the boolean myControl set to true, the expected output is that none of the fields are uppercased.
Which is what is happening:
{"title":"dr","firstName":"john","lastName":"doe"}
This means by default nothing will change.
If I call the mapper with a true for case serialization I only want the fields where the JsonSerialize(using = CaseSerializer.class) is present.
Any ideas?
I like to deserialize with Jackson an empty String member ("") to null. The Deserialization Feature "ACCEPT_EMPTY_STRING_AS_NULL_OBJECT" can for this unfortunately not be used (see link).
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(Include.NON_NULL)
public class Supplier {
private Integer id;
private String name;
private String image;
private String link;
private String description;
}
So after deserialization of the following JSON String the string members "link" and "image" should be null and not "".
{"id":37,"name":"Life","image":"","link":"","description":null}
I am looking for a way to write an own deserializer which can be used for String members of a POJO. Is there a way to achieve this? I am using faster Jackson 2.6.0.
The custom deserializer can be done as follows in Jackson 2.6.0.
public class SupplierDeserializer extends JsonDeserializer<Supplier> {
#Override
public Supplier deserialize(JsonParser jp, DeserializationContext context) throws IOException, JsonProcessingException {
Supplier sup = new Supplier();
JsonNode node = jp.readValueAsTree();
sup.setId(node.get("id").asInt());
sup.setDescription(node.get("description").asText());
String image = node.get("image").asText();
if("".equals(image)) {
image = null;
}
sup.setImage(image);
String link = node.get("link").asText();
if("".equals(link)) {
link = null;
}
sup.setLink(link);
sup.setName(node.get("name").asText());
return sup;
}
}
Register the custom deserialiser with the Supplier class
#JsonDeserialize(using = SupplierDeserializer.class)
public class Supplier {
private Integer id;
private String name;
private String image;
private String link;
private String description;
// getters and setters
}
Call the ObjectMapper class to parse the JSON data
String jsonData = "{\"id\":37,\"name\":\"Life\",\"image\":\"\",\"link\":\"\",\"description\":null}";
Supplier sup = new ObjectMapper().readValue(jsonData, Supplier.class);