JSON to Java object deserialization with escaped properties - java

I need to convert the following JSON to Java object. The property providerResponse in the JSON contains map of properties but they are escaped and wrapped in doubleQuotes. As a result, it does not deserialize the property providerResponse into a Java object (it comes as String). I use objectMapper.readValue(msgStr, classType) to deserialize the JSON. The message is generated by AWS for SNS delivery status notifications and I don't have control to change the JSON message. Is it possible to configure ObjectMapper to unescape the property and deserialize into a Java object instead of String?
{
"delivery":{
"providerResponse":"{\"sqsRequestId\":\"308ee0c6-7d51-57b0-a472-af8e6c41be0b\",\"sqsMessageId\":\"88dd59eb-c34d-4e4d-bb27-7e0d226daa2a\"}"
}
}
#JsonProperty("providerResponse")
private String providerResponse;

There doesn't seem to be a way to configure ObjectMapper to handle this behavior by default. The solution is to create a custom JsonDeserializer:
public class Wrapper {
public Delivery delivery;
}
public class Delivery {
#JsonDeserialize(using = ProviderResponseDeserializer.class)
public ProviderResponse providerResponse;
}
public class ProviderResponse {
public String sqsRequestId;
public String sqsMessageId;
}
public class ProviderResponseDeserializer extends JsonDeserializer<ProviderResponse> {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public ProviderResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return mapper.readValue(jsonParser.getText(), ProviderResponse.class);
}
}
Then you can deserialize the JSON by using your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(JSON, Wrapper.class);

I faced this similar issue. This gets resolved if we define a constructor in ProviderResponse which takes a single string argument (which is actually json) and then map the json in the constructor to the instance of ProviderResponse and use this temp instance to initialise the properties.
public class Wrapper {
public Delivery delivery;
}
public class Delivery {
public ProviderResponse providerResponse;
}
public class ProviderResponse {
public String sqsRequestId;
public String sqsMessageId;
private static ObjectMapper objMapper = new ObjectMapper();
public ProviderResponse(String json) {
ProviderResponse temp = objMapper.readValue(json, ProviderResponse.class);
this.sqsMessageId = temp.sqsMessageId;
this.sqsRequestId = temp.sqsRequestId;
}
}
The key is to keep the ObjectMapper instance and the its usage somewhere in your utility class and use it from there.

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.

Make Jackson deserialize into private fields without setter or constructor

I managed to configure Jackson to serialize a class without any getters or annotations on the private fields inside the class by just using
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
But I don't manage to make a JSON string of this non-static class to get deserialized into an object without any parameterized constructor or setters. Is this possible at all - I think it should through reflection but I don't get exactly how...
Here are 2 tests which need to pass to achieve what I need:
public class ObjectMapperTest {
private ObjectMapper mapper;
#Before
public void init() {
mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
#Test
public void serialize() throws Exception {
Payload payloadToSerialize = new Payload();
payloadToSerialize.data = "testData";
String serializedPayload = mapper.writeValueAsString(payloadToSerialize);
assertThat(serializedPayload, is("{\"data\":\"testData\"}"));
// --> OK
}
#Test
public void deserialize() throws Exception {
Payload deserializedPayload = mapper.readValue("{\"data\":\"testData\"}", Payload.class);
assertThat(deserializedPayload.data, is("testData"));
// com.fasterxml.jackson.databind.JsonMappingException:
// No suitable constructor found for type [simple type, class ...ObjectMapperTest$Payload]:
// can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
// at [Source: {"data":"testData"}; line: 1, column: 2]
}
public class Payload {
private String data;
public Payload() {
// empty constructor for Jackson
}
}
}
Making the Payload class static would fix the test but static classes are not an option for me as I am not working with inner payload classes in the project. Any ideas how to fix it through object mapper configuration change?
EDIT
As I am using the Jackson object mapper in a Spring MVC application to serialize / deserialize under the hood I need a solution which changes or extends the object mapper configuration only.
You can write your own deserializer to parse the JSON and create the instance of Payload, and then set the data value using reflection.
Exemple :
#Before
public void init() {
mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
mapper.registerModule(
new SimpleModule()
.addDeserializer(Payload.class, new JsonDeserializer<Payload>() {
#Override
public Payload deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException {
JsonNode obj = parser.readValueAsTree(); // Read the JSON as a node
Payload payload = new Payload();
if (obj.isObject() && obj.has("data")) { // The node is an object and has a "data" field
try {
// Use reflection to set the value
Field dataField = payload.getClass().getDeclaredField("data");
dataField.setAccessible(true);
dataField.set(payload, obj.get("data").asText());
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
throw new IOException("Reflection error", ex);
}
}
return payload;
}
}));
}
Edit: If you want something more "generic" you can try to create the instance yourself and change the accessibility of all the fields. Then you tell Jackson to update the values using the JSON.
public <T> T deserialize(final String json, final T instance) throws Exception {
for (Field field : instance.getClass().getDeclaredFields()) {
field.setAccessible(true);
}
mapper.readerForUpdating(instance).readValue(json);
return instance;
}
#Test
public void deserializeUpdate() throws Exception {
Payload deserializedPayload = deserialize("{\"data\":\"testData\"}", new Payload());
assertThat(deserializedPayload.data, is("testData"));
}
I tested this on your Payload class, maybe it doesn't work on more complex objects.

How java jackson deserializer handle both Boolean and Object on same field

I'm working with a 3rd party JSON API, it returns data like this:
{details: {...}, ...}
I use Java Jackson to deserialize this JSON string into a POJO object, the field declaration is :
#JsonProperty("details")
public Details getDetails(){...}
and Details is another class.
Everything is fine until I found that API may return data like this:
{details: false, ...}
If details is empty, it returns false!!! And jackson gave me this exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Details] from Boolean value; no single-boolean/Boolean-arg constructor/factory method (through reference chain: ...["details"])
So, how to handle this kind of JSON string? I only need this field to set to null if empty.
The error message from Jackson hints that the library has bulit in support for static factory methods. This is (perhaps) a simpler solution than a custom deserializer:
I created this example POJO, with a static factory method, annotated so that Jackson uses it:
public class Details {
public String name; // example property
#JsonCreator
public static Details factory(Map<String,Object> props) {
if (props.get("details") instanceof Boolean) return null;
Details details = new Details();
Map<String,Object> detailsProps = (Map<String,Object>)props.get("details");
details.name = (String)detailsProps.get("name");
return details;
}
}
test method:
public static void main(String[] args)
{
String fullDetailsJson = "{\"details\": {\"name\":\"My Details\"}} ";
String emptyDetailsJson = "{\"details\": false} ";
ObjectMapper mapper = new ObjectMapper();
try {
Details details = mapper.readValue(fullDetailsJson, Details.class);
System.out.println(details.name);
details = mapper.readValue(emptyDetailsJson, Details.class);
System.out.println(details);
} catch (Exception e) {
e.printStackTrace();
}
}
result is as expected:
My Details
null
Make a custom JsonDeserializer to handle deserializing your Details object in which you either return null if you get false or pass the object to the default deserializer if it's an actual object. Pseudocode:
public class CustomDeserializer extends JsonDeserializer<Details>{
#Override
public Details deserialize(JsonParser jsonParser, DeserializationContext ctx){
//if object use default deserializer else return null
}
}
You'll also have to write an ObjectMapperProvider to register your deserializer like so:
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
private ObjectMapper mapper;
public ObjectMapperProvider(){
mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(Details.class, new CustomDeserializer());
mapper.registerModule(sm);
}
public ObjectMapper getContext(Class<?> arg0){
return mapper;
}
}

Object Mapper mapping for a particular class during http calls

Is there a way where we can add ObjectMapper for a particular class through annotation.
#JsonRootName("employee")
public class Sample implements Serializable{
private String name;
private String id;
// Getters and setters
}
In the RestController i have RequestMapping and a method like:-
#ResponseBody
public Sample (#RequestBody Sample sample){
//some logic
return sample;
}
My input payload to this will be like
{
"employee":{
"name":"abcd",
"id":"1234"
}
}
My desired output would be
{
"name":"abcd",
"id":"1234"
}
1)Is there a way i can use the same class to fulfill the input and the output.
2) I have added #JsonRootName at the top of the class which requires ObjectMapper's Serialization feature enable to WRAP_ROOT_VALUE like :-
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
where this can be added to reflect in only this class.
Maybe just leave the default serialization behavior? Then, at deserialization you would still pull out the "employee" wrapper, but at serialization you would write it without the wrapper.
ObjectMapper mapper = new ObjectMapper();
//mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
With your input, I got the desired serialization output:
{"name":"abcd","id":"1234"}
EDIT
As for where to put this code, I'd recommend a singleton or class with static methods that handle your (de)serialization. You could have two different mappers than perform the "normal" or "wrapped" behavior. Here's an outline of the static method approach:
public class SerializationUtil {
private static ObjectMapper normalObjectMapper;
private static ObjectMapper wrappedObjectMapper;
static {
/* configure different (de)serialization strategies */
normalObjectMapper = new ObjectMapper();
wrappedObjectMapper = new ObjectMapper();
wrappedObjectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
wrappedObjectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
}
public static <T> T normalDeserialize(String json, Class<T> clazz) throws Exception {
return normalObjectMapper.readValue(json, clazz);
}
public static String normalSerialize(Object bean) throws Exception {
return normalObjectMapper.writeValueAsString(bean);
}
public static <T> T deserializeWrappedObject(String json, Class<T> clazz) throws Exception {
return wrappedObjectMapper.readValue(json, clazz);
}
public static String serializeWrappedObject(Object bean) throws Exception {
return wrappedObjectMapper.writeValueAsString(bean);
}
}
The benefit of this method is it allows the caller to decide the serialization behavior. So if there are portions of your code where you need to handle it differently you can call another method. Note that the wrapping/unwrapping are both enabled. So to get your desired behavior, you would call these methods like so:
public static void main(String[] args) {
Bean bean = SerializationUtil.deserializeWrappedObject(jsonInput, Bean.class);
String jsonOutput = SerializationUtil.normalSerialize(bean);
}
If this does not appeal to you, you could alternatively detect the special case and handle it in the same method call:
public static <T> T deserialize(String json, Class<T> clazz) throws Exception {
if (clazz instanceof Bean) {
return wrappedObjectMapper.readValue(json, clazz);
} else {
return normalObjectMapper.readValue(json, clazz);
}
}

How to deserialize a blank JSON string value to null for java.lang.String?

I am trying a simple JSON to de-serialize in to java object. I am however, getting empty String values for java.lang.String property values. In rest of the properties, blank values are converting to null values(which is what I want).
My JSON and related Java class are listed below.
JSON string:
{
"eventId" : 1,
"title" : "sample event",
"location" : ""
}
EventBean class POJO:
public class EventBean {
public Long eventId;
public String title;
public String location;
}
My main class code:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
try {
File file = new File(JsonTest.class.getClassLoader().getResource("event.txt").getFile());
JsonNode root = mapper.readTree(file);
// find out the applicationId
EventBean e = mapper.treeToValue(root, EventBean.class);
System.out.println("It is " + e.location);
}
I was expecting print "It is null". Instead, I am getting "It is ". Obviously, Jackson is not treating blank String values as NULL while converting to my String object type.
I read somewhere that it is expected. However, this is something I want to avoid for java.lang.String too. Is there a simple way?
Jackson will give you null for other objects, but for String it will give empty String.
But you can use a Custom JsonDeserializer to do this:
class CustomDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.readValueAsTree();
if (node.asText().isEmpty()) {
return null;
}
return node.toString();
}
}
In class you have to use it for location field:
class EventBean {
public Long eventId;
public String title;
#JsonDeserialize(using = CustomDeserializer.class)
public String location;
}
It is possible to define a custom deserializer for the String type, overriding the standard String deserializer:
this.mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StdDeserializer<String>(String.class) {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String result = StringDeserializer.instance.deserialize(p, ctxt);
if (StringUtils.isEmpty(result)) {
return null;
}
return result;
}
});
mapper.registerModule(module);
This way all String fields will behave the same way.
You might first like to see if there has been any progress on the Github issue requesting this exact feature.
For those using Spring Boot: The answer from jgesser was the most helpful to me, but I spent a while trying to work out the best way to configure it in Spring Boot.
Actually, the documentation says:
Any beans of type com.fasterxml.jackson.databind.Module are
automatically registered with the auto-configured
Jackson2ObjectMapperBuilder and are applied to any ObjectMapper
instances that it creates.
So here's jgesser's answer expanded into something you can copy-paste into a new class in a Spring Boot application
#Configuration
public class EmptyStringAsNullJacksonConfiguration {
#Bean
SimpleModule emptyStringAsNullModule() {
SimpleModule module = new SimpleModule();
module.addDeserializer(
String.class,
new StdDeserializer<String>(String.class) {
#Override
public String deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
String result = StringDeserializer.instance.deserialize(parser, context);
if (StringUtils.isEmpty(result)) {
return null;
}
return result;
}
});
return module;
}
}
I could get this by following configuration.
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
it is possible to use JsonCreator annotation. It worked for me
public class Foo {
private String field;
#JsonCreator
public Foo(
#JsonProrerty("field") String field) {
this.field = StringUtils.EMPTY.equals(field) ? null : field ;
}
}

Categories

Resources