I wrote a Custom Serializer and Custom Deserializer to serialize properties marked with #Confidential annotation.
#Data
public class Person {
private String name;
#Confidential
private String address;
}
The Custom Serializer serializes a POJO with following values:
{ "name": "John Doe", "address": "Kearney St"}
as follows:
{"name":"John Doe", "address": {"value":"IjIwMzEwIDU4dGggTG4gTkUi"}}
The Custom Deserializer is also able to deserialize the JSON back to the Person POJO fine.
However, when I make the fields in the Person POJO final, serialization continues to work, but deserialization fails.
#Data
public class Person {
private final String name;
#Confidential
private final String address;
}
Here's the BeanSerializerModifier implementation:
#AllArgsConstructor
public class CustomDeserializerModifier extends BeanDeserializerModifier {
private final ObjectMapper objectMapper;
#Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config,
final BeanDescription beanDesc,
final BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> beanPropertyIterator = builder.getProperties();
beanPropertyIterator.forEachRemaining(settableBeanProperty -> {
final Confidential annotation = settableBeanProperty.getAnnotation(Confidential.class);
if (encryptedProperty != null) {
JsonDeserializer<Object> current = settableBeanProperty.getValueDeserializer();
final SettableBeanProperty newSettableBeanProperty =
settableBeanProperty.withValueDeserializer(
new CustomDeserializer(annotation, current, objectMapper)
);
builder.addOrReplaceProperty(newSettableBeanProperty, true);
}
});
return builder;
}
}
I found that CustomDeserializer, never gets called when the Person POJO fields are final.
Here's the error message:
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: {"name":"John Doe","address":{"value":"IjIwMzEwIDU4dGggTG4gTkUi"}}; line: 1, column: 30] (through reference chain: com.custom.model.Person["address"])
Can a Jackson expert please tell me why my CustomDeserializer isn't getting invoked when the POJO fields are final.
Thank you!
As mentioned, serialization will perfectly work for both mutable and immutable fields. Deserialization issue will only occur when using immutable fields as the BeanDeserializerModifier won't work in such a case.
In the Jackson terminology, immutable fields are named creator properties, meaning they are initialized using creators. See BeanDeserializerBase#resolve.
To correctly handle this use case, the ObjectMapper may be created with a custom DeserializationContext (an extended implementation of the ObjectMapper may also set the protected field related to the deserialization context).
Then, through an override of the method DeserializationContext#handleSecondaryContextualization, it will be possible to change the deserialization to make it work.
There is maybe other possibilities but this one is working fine with encryption.
Related
I have a Json response that looks like this:
[
{ "name":"A" },
{ "name":"B" }
]
I have Java classes representing a single ResponseDto and contains a List of Person:
public class GetPersonsResponseDto {
public List<Person> persons;
}
public class Person {
public String name;
}
I would like to deserialize the by JSON using ObjectMapper but without use of a custom Deserializer and without collection type (no Persons[].class or TypeReference<List<Person>>(){}). What I really want is
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(in, GetPersonsResponseDto.class);
But I get:
jackson.map.JsonMappingException:
Can not deserialize instance of com.project.my.GetPersonsResponseDto out of START_ARRAY token
I tried several Annotations but without success.
Actually it is quite simple to serialize your models to the target json, but might be tricky to deserialize.
So, the solution for deserialization in this case could be using #JsonCreator annotation from com.fasterxml.jackson.annotation package above constructor:
#Data
public class GetPersonsReponseDto {
public List<Person> persons;
#JsonCreator // use this annotation for deserialization on constructor
public GetPersonsReponseDto(List<Person> persons) {
this.persons = persons;
}
public GetPersonsReponseDto() {
}
}
However, it might not work with some versions of jackson.
I have a generic class
#NoArgsConstructor
public class CustomData<T> {
}
Below is the class it is used.
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Recipient {
#NotEmpty(message = "Please provide email details of recipient")
public String email;
public CustomData<?> custom_data;
}
Below is the payload I'm trying to use
"recipients": [
{
"custom_data": {},
"email": "string"
}
]
However I get an error saying com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class
Can someone please help? Thank you for your time
Please add the property SerializationFeature.FAIL_ON_EMPTY_BEANS= false to your object mapper like objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//or you can add this code to your any class of springboot which have #Configuration annotation on it.
#Bean
public ObjectMapper getMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return objectMapper;
}
Send Payload as
{
"custom_data": {},
"email": "emailvalue"
}
//Now I am supposing your api is like this
#PostMapping(value = "/show/recipient") /* /preview-email-recipient*/
public ResponseEntity<?> showRecipient(#Valid #RequestBody Recipient recipient){
log.info(String.format("recipient received {%s} ",recipient.toString()));
return new ResponseEntity<>(recipient,HttpStatus.OK);
}
//curl call will be
endpoint will be post call : http://localhost:8080/show/recipient
with requestbody as : {
"customData": {},
"email": "emailvalue"
}
response : {
"customData": {},
"email": "emailvalue"
}
Reason for failure was ?
0
when you return your object i.e. (Recipient in this case) in response it is getting Serialized to json string using ObjectMapper which is used in spring's MessageConverter(i.e Jackson2HttpMessageConverter) bean. Now the error is caused due to how ObjectMapper serializes your class. Your class has 2 field, 1 of type String and 1 of type JSONObject/GenericType. ObjectMapper when serializing fields, tries to find the corresponding serializer based on the field type. There are some out-of-the-box implementation of serializer for known type like String but for your custom type you either need to provide serializer to ObjectMapper bean or have to disable serialization via configuration of set property SerializationFeature.FAIL_ON_EMPTY_BEANS to false.
Now what does SerializationFeature.FAIL_ON_EMPTY_BEANS do??
public static final SerializationFeature FAIL_ON_EMPTY_BEANS
Feature that determines what happens when no accessors are found for a type (and there are no annotations to indicate it is meant to be serialized). If enabled (default), an exception is thrown to indicate these as non-serializable types; if disabled, they are serialized as empty Objects, i.e. without any properties.
Note that empty types that this feature has only effect on those "empty" beans that do not have any recognized annotations (like #JsonSerialize): ones that do have annotations do not result in an exception being thrown.
Feature is enabled by default.
So
1 way was to disable serialization on empty beans.
2nd way you can annotate CustomData class with #JsonSerialize i.e. you
are provinding the mapper which serializer you have to used for this
param.
so Make CustomData class as---
#NoArgsConstructor
#JsonSerialize
public class CustomData<T> {
}
I'm using Jackson to deserialize some JSON and I've run into some trouble while trying to use a custom deserializer for one of the fields.
class MyClass
{
private static class SpecialPropertyDeserializer extends JsonDeserializer<SpecialProperty>
{
#Override
public SpecialProperty deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException
{
// do some custom deserialisation
}
}
private static class SpecialProperty
{
private String m_foo;
private String m_bar;
#JsonCreator
SpecialProperty(#JsonProperty("foo") String foo,
#JsonProperty("bar") String bar)
{
m_foo = foo;
m_bar = bar;
}
}
private String m_identifier;
private String m_version;
#JsonDeserialize(using = SpecialPropertyDeseializer.class)
private SpecialProperty m_specialProperty;
#JsonCreator
MyClass(#JsonProperty("identifier") String identifier,
#JsonProperty("version") String version,
#JsonProperty("specialProperty") SpecialProperty specialProperty)
{
m_identifier = identifier;
m_version = version;
m_specialProperty = specialProperty;
}
}
and this is the JSON I want to deserialize:
{
"identifier" : "some-id",
"version" : "1.7",
"specialProperty" : {
"foo" : "str1",
"bar" : "str2"
},
}
I invoke the mapper as follows:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
return objectMapper.readValue(input, MyClass.class);
I've observed the following behaviour:
Without a special property it all works fine - i.e. remove all
references to SpecialProperty from the code and the JSON.
If I include SpecialProperty in the JSON but remove the custom
deserializer for it then it also works fine. The ctor for
SpecialProperty is called.
With the custom deserializer it doesn't work. The ctor for SpecialProperty is called but the custom deserializer is not.
What am I doing wrong?
#JsonDeserialize annotation can be placed on a field, a setter or a class. Jackson will take it into account if what is annotated is what it uses to set the value.
E.g.1 It will notice #JsonDeserialize over a setter if it uses the setter to set the value of a field.
E.g.2 It will notice #JsonDeserialize over a field if it directly sets this field without using a setter or a constructor.
It will tend to take it into account if it's on a class unless it's overridden by a more specific annotation on a field or setter docs. I reckon the docs could be clearer on the above details.
In your case you have the annotation over the SpecialProperty field but you are setting this field in the MyClass constructor so it's ignored.
In this case you can move #JsonDeserialize over the class instead of over the field. That's probably the simplest solution in your case. E.g.
#JsonDeserialize(using = MyClass.SpecialPropertyDeserializer.class)
private static class SpecialProperty {
Or you can skip the annotation altogether and register the deserializer on the mapper. First make SpecialProperty and SpecialPropertyDeserializer non private in MyClass and then:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(MyClass.SpecialProperty.class, new MyClass.SpecialPropertyDeserializer());
objectMapper.registerModule(module);
You can also get rid of constructor of MyClass and the current annotation over the SpecialProperty field will be taken into account.
I'm trying to (de)serialize an object that has a property with a type that comes from a maven dependency, so I can't change the class of this type.
The class of this type has a #JsonSerialize and #JsonDeserialize annotation.
However, I want to use the default serializer and deserialzer, because the custom serializer writes an array instead of an object. Is there a way, using annotations, to tell jackson to use the default (de)serializer?
You can disable the annotations using Jackson's mixins feature.
In the following example, any attempt at deserializing to a CustomerObj will result in an exception due to its defective Builder:
#JsonDeserialize(builder = CustomerObj.class)
public class CustomerObj {
public String name;
public int age;
public CustomerObj build() {
throw new RuntimeException("JsonDeserializer invoked");
}
}
Create a mixin with a JsonDeserialize annotation that disables the broken builder:
#JsonDeserialize(builder = java.lang.Void.class)
public static abstract class CustomerMixin { }
Register the mixin on the ObjectMapper instance:
ObjectMapper om = new ObjectMapper();
om.addMixIn(CustomerObj.class, CustomerMixin.class);
Enjoy working deserialization:
final String json = "{\"name\":\"Brian\",\"age\":41}";
CustomerObj customer = om.readValue(json, CustomerObj.class);
My REST service returns following JSON
{
"name": "John",
"id" : 10
}
Can I use Jersey to marshall it into following Bean:
public class User{
private String name;
//getter & setter
}
I wanted to do this with following code but it doesn't work
WebResource webResource = client.resource(url);
webResource.accept(MediaType.APPLICATION_JSON_TYPE);
User user = webResource.get(User.class);
Is this even possible or I have to implement full JSON structure in Java Beans to get it work?
I know that I can parse this JSON with Jackson and any other methods.
With Jackson, easiest way is to configure ObjectMapper like so:
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
Check this sample provider
package com.company.rest.jersey;
#Provider
#Component
#Produces({MediaType.APPLICATION_JSON})
public class JacksonMapperProvider implements ContextResolver<ObjectMapper> {
ObjectMapper mapper;
public JacksonMapperProvider(){
mapper = new ObjectMapper();
mapper.configure(Feature.INDENT_OUTPUT, true);
// Serialize dates using ISO8601 format
// Jackson uses timestamps by default, so use StdDateFormat to get ISO8601
mapper.getSerializationConfig().setDateFormat(new StdDateFormat());
// Deserialize dates using ISO8601 format
// MilliDateFormat simply adds milliseconds to string if missing so it will parse
mapper.getDeserializationConfig().setDateFormat(new MilliDateFormat());
// Prevent exceptions from being thrown for unknown properties
mapper.configure(
DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
}
#Override
public ObjectMapper getContext(Class<?> aClass) {
return mapper;
}
}
With Jackson :
You have two options:
Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
Or, you can use the #JsonIgnore annotation of Jackson on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
In your bean, add the annotation #JsonIgnoreProperties(ignoreUnknown = true) at the class level and it should skip the id property in the JSON since it's not present in the bean.
#JsonIgnoreProperties(ignoreUnknown = true)
public class User{
private String name;
//getter & setter
}
(See http://wiki.fasterxml.com/JacksonAnnotations for details)