Jackson deserialize child JSON to parent attribute - java

This is my requirement. I have below POJO.
class Car {
private String brandName;
private String color;
private Model model;
}
class Model {
private String modelName;
private String year;
}
If I get input json like below, then it should be de-serialized and mapped to both classes.
String json = "{\"brandName\" : \"Toyoto\", \"color\" : \"Silver\", \"model\" : {\"modelName\": \"Corolla\", \"year\": \"2019\"}}"
ObjectMapper mapper = new ObjectMapper();
Car car = mapper.readValue(json, Car.class);
assertEquals("Corolla", car.getModel().getModelName());
This case is fine.
But if I pass child json, that also should work without changing mapping class.
String json = "{\"modelName\": \"Corolla\", \"year\": \"2019\"}"
ObjectMapper mapper = new ObjectMapper();
Car car = mapper.readValue(json, Car.class);
assertEquals("Corolla", car.getModel().getModelName());
Any idea to solve this problem

You can implement your custom deserializer for Car class like:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
/**
* #author Ehsan Zaery Moghaddam
*/
public class CarDeserializer extends StdDeserializer<Car> {
public CarDeserializer() {
this(null);
}
public CarDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Car deserialize(JsonParser jp, DeserializationContext dctx)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
Car c = new Car();
Model cModel = new Model();
if(node.has("brandName")) {
// the JSON string contains both car and model details
c.setBrandName(node.get("brandName").asText());
c.setColor(node.get("color").asText());
JsonNode modelNode = node.get("model");
cModel.setModelName(modelNode.get("modelName").asText());
cModel.setYear(modelNode.get("year").asText());
} else {
// the JSON string just has model details
cModel.setModelName(node.get("modelName").asText());
cModel.setYear(node.get("year").asText());
}
c.setModel(cModel);
return c;
}
}
and when you're going to call the Jackson API to do the actual deserialization, register your deserializer in advance:
String json = "{\"modelName\": \"Corolla\", \"year\": \"2019\"}";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Car.class, new CarDeserializer());
mapper.registerModule(module);
Car car = mapper.readValue(json, Car.class);
assertEquals("Corolla", car.getModel().getModelName());
This doesn't require to alter your POJOs. However, if you could do that, you had an option to register your custom deseriallizers using an annotation in your POJO class as below:
#JsonDeserialize(using = CarDeserializer.class)
public class Car { ... }

Related

Jackson include a key that is empty string

I accept from a server a json like this:
{
"": "hello"
}
And in Jackson I did
#JsonProperty("")
private String string
When deserialising the object it ignores the property completely.
How can I make an empty string count as a key?
Thank you
I found a way to achieve what you want with custom deserializer by following steps.
Step 1: Create the POJO class you want to deserialize to
public class MyPojo {
private String emptyFieldName;
//constructor, getter, setter and toString
}
Step 2: Create your custom deserializer
public class MyDeserializer extends StdDeserializer<MyPojo> {
public MyDeserializer () {
this(null);
}
protected MyDeserializer (Class<?> vc) {
super(vc);
}
#Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
String emptyFieldName = jsonNode.get("").asText();
return new MyPojo(emptyFieldName);
}
}
Step 3: Register this custom deserializer
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(MyPojo.class, new MyDeserializer());
objectMapper.registerModule(module);
MyPojo myPojo = objectMapper.readValue(jsonStr, MyPojo.class);
System.out.println(myPojo.getEmptyFieldName());
Console output:
hello
BTW, you could also directly register this custom deserializer on the class:
#JsonDeserialize(using = MyDeserializer.class)
public class MyPojo {
...
}
For more information, please refer to Getting Started with Custom Deserialization in Jackson.

JaxbDto Serialization and deserialization

I need to receive some message with SOAP so I've generated a few classes by xsd-scheme and maven-jaxb2-plugin like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Claim", propOrder = {
"field",
})
public class ClaimType {
#XmlElement(required = true, type = Integer.class, nillable = false)
protected Integer field;
public Integer getField() {
return bpType;
}
public void setField(Integer value) {
this.field= value;
}
}
After receiving message I need to send these to the next one microservice in wrap of HashMap.
I supposed to use ObjectMapper to convert:
//JAXB DTO --> JSON
ObjectMapper objectMapper = new ObjectMapper();
String jsonContent = objectMapper.writeValueAsString(claimType);
map.put("json", jsonContent);
//JSON --> JAXB DTO
ObjectMapper objectMapper = new ObjectMapper();
String json = map.get("json");
ClaimType claimType = objectMapper.readValue(json, ClaimType.class);
But the generated classes are haven't any constructors so I got the exception like "
No creator like default constructor are exists".
What is the best preactice to work with Jaxb Dto? Can I do smth to successful convert these json to object? Thanks in advance!
I've solved my problem by using ObjectMapper MixIn:
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
#JsonIgnoreProperties(value = {"globalScope", "typeSubstituted", "nil"})
public abstract class JAXBElementMixIn<T> {
#JsonCreator
public JAXBElementMixIn(#JsonProperty("name") QName name,
#JsonProperty("declaredType") Class<T> declaredType,
#JsonProperty("scope") Class scope,
#JsonProperty("value") T value) {
}
}
And the convertation:
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(JAXBElement.class, JAXBElementMixIn.class);
solution link

Value of a json key is another valid json itsef. Can this value be parsed as a String instead of parsing the inner json as json?

I have the following code which should parse a simple json as follows:
{"jsonTest":
{"innerKey":"innerValue"}
}
Here, the value of 'jsonTest' is a json object.
Question : Can this JSON be parsed (using jackson) and retrieve the value of 'jsonTest' as a simple String? In other words, is there a way to ask the parser not to parse the inner json object?
here is what I tried so far. This results in a parsing exception
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonParserTest {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"jsonTest\":{\"innerKey\":\"innerValue\"}}";
TestJson js = mapper.readValue(json, TestJson.class);
System.out.println(js.getJsonTest());
}
}
class TestJson {
private String jsonTest;
public String getJsonTest() {
return jsonTest;
}
public void setJsonTest(String jsonTest) {
this.jsonTest = jsonTest;
}
}
You can use JsonNode for this. Just change your TestJson class:
class TestJson {
private JsonNode jsonTest;
public JsonNode getJsonTest() {
return jsonTest;
}
public void setJsonTest(JsonNode jsonTest) {
this.jsonTest = jsonTest;
}
}
Output:
{"innerKey":"innerValue"}
Also you can do it just using the JsonNode without the TestJson class.
Use the following mapper.readTree method:
JsonNode jsonNode = mapper.readTree(json);
System.out.println(jsonNode.get("jsonTest").toString());
You should create structure to deseralize that json. It could be something like this:
#Data
#NoArgsConstructor
class TestJson {
JsonTest jsonTest;
#Data
#NoArgsConstructor
private static class JsonTest {
String innerKey;
}
}

How do I ignore the root node when reading in a json rest api?

I am reading in a rest api that returns a Json file. I need to ignore the "result" and "optionChain" nodes. I am using Spring Boot with Jackson to deal with the mapping of objects.
Thanks in Advance!
For the Json File click here
Here is my Main:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
#SpringBootApplication
public class OptionsImpliedMovementApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(OptionsImpliedMovementApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
String resourceURL = url;
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(resourceURL, HttpMethod.GET,entity, String.class);
String rawJson = response.getBody();
ObjectMapper mapper = new ObjectMapper();
//need to read in json ignoring root node
}
Since you're already getting JSON response , then would recommend to use
restTemplate.exchange(URL, HttpMethod.GET, requestEntity, MyPOJO.class);
instead of String.class define your own POJO based on JSON response coming as you have attached in file.json.
In a handy way you can generate your POJO against your JSON from : http://www.jsonschema2pojo.org/ quickly and easily.
So it should look like :
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"optionChain"
})
public class MyPOJO {
#JsonProperty("optionChain")
private OptionChain optionChain;
// getters and setters
}
And another one :
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"result",
"error"
})
public class OptionChain {
#JsonProperty("result")
private List<Result> result = null;
#JsonProperty("error")
private Object error;
// getter and setters
}
And other like :
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"expirationDate",
"hasMiniOptions",
"calls",
"puts"
})
public class Option {
#JsonProperty("expirationDate")
private Integer expirationDate;
#JsonProperty("hasMiniOptions")
private Boolean hasMiniOptions;
#JsonProperty("calls")
private List<Call> calls = null;
#JsonProperty("puts")
private List<Put> puts = null;
So once you'll have the response as :
ResponseEntity<MyPOJO> response = restTemplate.exchange(resourceURL, HttpMethod.GET,entity, MyPOJO.class);
Then response.getBody will give the content inside optionChain node which is what you're looking for. You can then normally drill down to whatever node you want as now you have everything in plain java objects and you can ignore whatever you want or use whatever is needed.
Using objectMapper also you can then achieve the same :
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo= mapper.readValue(rawJson, MyPojo.class);
Quick (performant) and working.
final ObjectMapper objectMapper = new ObjectMapper();
final JsonNode jsonNode = objectMapper.readTree(rawJson);
final JsonNode result = jsonNode.get("optionChain")
.get("result");
final JsonNode firstResult = result.get(0);
final YourResultClass resultObject = objectMapper.treeToValue(firstResult, YourResultClass.class);
If you need to ignore unknown fields
final ObjectMapper objectMapper =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

Adding Custom deserialization class to already exsisting #JsonDeserialize annotation using jackson

I am trying to deserialize json by writing custom deserializer.
Here is my code.
public class EventLoginDeserializer extends StdDeserializer<EventLogin> {
public EventLoginDeserializer() {
this(null);
}
public EventLoginDeserializer(Class<EventLogin> event) {
super(event);
}
#Override
public EventLogin deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
return EventLogin.builder().displayName(jsonNode.get("display_name"))
.timestamp(DateTime.now()).build();
}
#Override
public Class<EventLogin> handledType() {
return EventLogin.class;
}
}
And here is the snippet of my main class.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
SimpleModule module = new SimpleModule();
module.addDeserializer(EventLogin.class, new EventLoginDeserializer());
mapper.registerModule(module);
String json = "{\"display_name\": \"Test Deserialization\", \"user_name\": \"test\"}";
EventLogin eventLogin = mapper.readValue(json, EventLogin.class);
System.out.println("readValue :::: " + eventLogin);
I have a requirement wherein I've to take an already existing #JsonDeserialize annotated model class in the jar file and add one more deserialization class above. I meant here is the same of an already existing class in a source file.
#AutoValue
#JsonDeserialize(builder = AutoValue_EventLogin.Builder.class)
#JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class EventLogin
{
public abstract String displayName();
public abstract DateTime timestamp();
#AutoValue.Builder
public abstract static class Builder implements Login.Builder<Builder> {
public abstract Builder displayName(String displayName);
public abstract Builder emailId(DateTime timestamp);
public abstract EventLogin build();
}
}
The problem is that since #JsonDeserialize already exists in the jar file, so adding custom deserializer was not being considered at all. Meaning, the overridden deserialize method the custom deserializer class is not being executed.
So how to overcome this problem?
Go the solution. Jackson lets us override those attribute-specified values with mixins, so we could create a mixin class and add in the object mapper.
#JsonDeserialize(using = EventLoginDeserializer.class)
public static class EventLoginMixIn{}
and then in our main class
ObjectMapper mapperWithMixin = new ObjectMapper()
.addMixIn(EventLogin.class, EventLoginMixIn.class);
// Deserialize the eventlogin
EventLogin eventLogin = mapperWithMixin.readValue(actualJson, EventLogin.class);

Categories

Resources