Custom deserialization with Jackson: extend default deserializer - java

I would like to make my own deserializer by extending the default one an setting some more values after it:
simplified code:
public class Dto {
public String originalJsonString;
}
public MyFooDto extends Dto {
public String myField;
}
#Bean
public ObjectMapper deserializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(MyFooDto.class, new JsonDtoDeserializer<>());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
// or maybe instead of the Beam just #JsonDeserialize(using = JsonDtoDeserializer.class) before MyFooDto?
public class JsonDtoDeserializer<T extends Dto> extends StdDeserializer<T> {
// or maybe extends JsonDeserializer? or UntypedObjectDeserializer? or UntypedObjectDeserializer.Vanilla?
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// here I would like:
T item = super.deserialize"AsUsual"(p, ctxt);
// the difficulty is to avoid the loop of death, where the deserializer would call itself for the eternity...
// And then set other Dto-fields depending on the original Json input, for example:
item.originalJsonString = p.readValueAsTree().toString();
return item;
}
}
As you can see, I would also like to reuse this Dto mother class for other DTOs.
I didn't find any example of it. I am really the first one in the world?
what should be the deserialize"AsUsual"(p, ctxt)?
what motherclass should I use? JsonDeserializer / StdDeserializer / UntypedObjectDeserializer?
Will the deserializer know which class of T it has to instantiate?
Thank you Community!

As Sharon said (based on How do I call the default deserializer from a custom deserializer in Jackson)
#Bean
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (Dto.class.isAssignableFrom(beanDesc.getBeanClass())) {
return new JsonDtoDeserializer<>(deserializer, beanDesc.getBeanClass());
}
return deserializer;
}
});
objectMapper.registerModule(simpleModule);
return objectMapper;
}
public class JsonDtoDeserializer<T extends Dto> extends StdDeserializer<T> implements ResolvableDeserializer /*StdDeserializer<Dto<T>>*/ /*UntypedObjectDeserializer.Vanilla*/ /*<T>*/ /*implements ResolvableDeserializer*/ {
private final JsonDeserializer<?> defaultDeserializer;
public JsonDtoDeserializer(JsonDeserializer<?> defaultDeserializer, Class<?> clazz) {
super(clazz);
this.defaultDeserializer = defaultDeserializer;
}
#Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
#SuppressWarnings("unchecked")
T itemObj = (T) defaultDeserializer.deserialize(p, ctxt);
return itemObj;
}
// for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
// otherwise deserializing throws JsonMappingException??
#Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
{
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}

Related

Throw error if strings are not double quoted while using jackson objectmapper deserialization

I have a JSON:
{
"stringField" : 1234,
"booleanField": true,
"numberField": 1200.00
}
I use object mapper to deserialize the json into:-
#Data
class SomeClass {
String stringField;
boolean booleanField;
float numberField;
}
I would like the objectMapper to throw an error because, the values for String fields must be double quoted according to the json spec. How can i get objectMapper to throw an error?
You can write custom string deserializer.(i assume you are using spring)
#Configuration
public class JacksonConfiguration {
#Bean
SimpleModule jacksonDeserializerConfiguration() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StdDeserializer<String>(String.class) {
#Override
public String deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
if (!parser.hasToken(JsonToken.VALUE_STRING)) {
//throw ex what do u want
throw new RuntimeException("String not include quote");
}
return StringDeserializer.instance.deserialize(parser, context);
}
});
return module;
}
}
This should fix your issue.
class SomeClass {
#JsonDeserialize(using=ForceStringDeserializer.class)
public String stringField;
public boolean booleanField;
public float numberField;
}
class ForceStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
throw deserializationContext.wrongTokenException(jsonParser, JsonToken.VALUE_STRING, "Attempted to parse Integer to String but this is forbidden");
}
return jsonParser.getValueAsString();
}
}
You just need to setup jackson objectmapper like this
JsonFactory factory = new JsonFactory();
factory.disable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
ObjectMapper mapper = new ObjectMapper(factory)
This should throw error during serialization/deserilaization

How to access default jackson serialization in a custom serializer with the same object

Copy here an old answer, that only worked to some people.
The question is,
Where should I locate the register part of the BeanSerializerModifier ?
what do I do with the ObjectMapper object ?
A BeanSerializerModifier will provide you access to the default serialization.
Inject a default serializer into the custom serializer
public class MyClassSerializer extends JsonSerializer<MyClass> {
private final JsonSerializer<Object> defaultSerializer;
public MyClassSerializer(JsonSerializer<Object> defaultSerializer) {
this.defaultSerializer = checkNotNull(defaultSerializer);
}
#Override
public void serialize(MyClass myclass, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (myclass.getSomeProperty() == true) {
provider.setAttribute("special", true);
}
defaultSerializer.serialize(myclass, gen, provider);
}
}
Create a BeanSerializerModifier for MyClass
public class MyClassSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass() == MySpecificClass.class) {
return new MyClassSerializer((JsonSerializer<Object>) serializer);
}
return serializer;
}
}
Register the serializer modifier
ObjectMapper om = new ObjectMapper()
.registerModule(new SimpleModule()
.setSerializerModifier(new MyClassSerializerModifier()));

Jackson custom deserialization - can't read JsonParser twice

I am trying to implement a Jackson Deserializer to have a way of migrating JSONs when they change (like a renamed field, for example, where I would need to read a now inconsistent JSON into a consistent one).
One way of doing this would be to create a JsonDeserializer, read the JSON as the proper final class, then read it again as a MAP, to cherry pick the changes.
I can't seem to do that though, because everytime I read or deserialize the JSON, the backing Stream closes.
class CustomDeserializer extends StdDeserializer<MyPOJO> implements ResolvableDeserializer{
private final JsonDeserializer<?> deserializer;
private final ObjectMapper mapper;
public CustomDeserializer(JsonDeserializer<?> deserializer, ObjectMapper mapper) {
super(MyPOJO.class);
this.deserializer = deserializer;
this.mapper = mapper;
}
#Override
public MyPOJO deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyPOJO deserialized = (MyPOJO) deserializer.deserialize(jp, ctxt);
// Custom migration would go here...
return deserialized;
}
#Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) deserializer).resolve(ctxt);
}
}
I am avoiding creating new ObjectMapper because there is already a customized one, with a custom Date deserializer, for example, so when I am using MyPOJO custom deserializer I want to be able to somehow delegate the deserialization in some way so it uses all previous configuration.
This is what I came up with:
#Autowired
public Serializer(List<IDeserializer> deserializers) {
SimpleModule module = new SimpleModule("name");
registerDeserializers(module, deserializers);
mapper.registerModule(module);
mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
}
private void registerDeserializers(SimpleModule module, List<IDeserializer> deserializers) {
if (CollectionUtils.isNotEmpty(deserializers)) {
final Map<Class<?>, IDeserializer> deserializerRegistryMap = toMap(deserializers);
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (deserializerRegistryMap.containsKey(beanDesc.getBeanClass())){
return deserializerRegistryMap.get(beanDesc.getBeanClass()).getDeserializer(mapper);
}
return super.modifyDeserializer(config, beanDesc, deserializer);
}
});
}
}
#Component
public class MigrationDeserializer {
#Autowired
private MigratorRegistry migratorRegistry;
public <T> JsonDeserializer<T> createDeserializer(final ObjectMapper mapper, final Class<T> returnType, final Class<? extends SerializedObjectsHistory<T>> historyType){
return new StdDeserializer<T>(returnType){
#Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
SerializedObjectsHistory<T> history = mapper.readValue(p, historyType);
return migratorRegistry.migrate(history, returnType);
}
};
}
}
On the serializers I am deserializing my POJO into "HistoryPOJO" which is basically the same thing as my POJO but with all the fields, even removed/renamed ones.
Then I transform it into the current version.

Jackson Modules for Map Serialization

I have a Class that contains a Map (with non String key) and some other fields.
public class MyClass() {
private Map<KeyObject, OtherObject> map;
private String someField;
public MyClass(Map<KeyObject, OtherObject> map, String someField) {
this.map = map;
this.someField = someField;
}
// Getters & Setters
}
I would like to serialize and deserialize this class using Jackson.
I saw a different ways of doing that and decided to try using jackson modules.
I followed this post and extended JsonDeserializer and JsonSerializer. The problem is that those classes should be typed, so it should look like
public class keyDeserializer extends JsonDeserializer<Map<KeyObject, OtherObject>> {
...
}
The same for the KeySerializer.
Then adding to the module:
module.addSerializer(new keySerializer());
module.addDeserializer(Map.class, new keyDeserializer());
But this is wrong apparently since I'm getting an exception:
keySerializer does not define valid handledType() -- must either register with method that takes type argument or make serializer extend 'org.codehaus.jackson.map.ser.std.SerializerBase'
I could have my serializer and deserializer to be typed to MyClass, but then I had to manually parse all of it, which is not reasonable.
UPDATE:
I managed to bypass the module creation in the code by using annotations
#JsonDeserialize(using = keyDeserializer.class)
#JsonSerialize(using = keySerializer.class)
private Map<KeyObject, OtherObject> map;
But then I have to serialize/deserialize the whole map structure on my own from the toString() output. So tried a different annotation:
#JsonDeserialize(keyUsing = MyKeyDeserializer.class)
private Map<KeyObject, OtherObject> map;
Where MyKeyDeserializer extends org.codehaus.jackson.map.KeyDeserializer and overriding the method
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {...}
Then manually deserializing my key but again from the toString() output of my key class.
This is not optimal (this dependency on the toString() method). Is there a better way?
Ended up using this serializer:
public class MapKeySerializer extends SerializerBase<Object> {
private static final SerializerBase<Object> DEFAULT = new StdKeySerializer();
private static final ObjectMapper mapper = new ObjectMapper();
protected MapKeySerializer() {
super(Object.class);
}
#Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return DEFAULT.getSchema(provider, typeHint);
}
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
if (null == value) {
throw new JsonGenerationException("Could not serialize object to json, input object to serialize is null");
}
StringWriter writer = new StringWriter();
mapper.writeValue(writer, value);
jgen.writeFieldName(writer.toString());
}
}
And this Deserializer:
public class MapKeyDeserializer extends KeyDeserializer {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return mapper.readValue(key, MyObject.class);
}
}
Annotated my Map:
#JsonDeserialize(keyUsing = MapKeyDeserializer.class)
#JsonSerialize(keyUsing = MapKeySerializer.class)
private Map<KeyObject, OtherObject> map;
This is the solution that worked for me, hope this helps other.

Customize jackson unmarshalling behavior

I am using Jackson fasterxml for unmarshalling JSON. In my object there are two kinds of properties:Input properties and Calculated properties. In the input JSON, I get only input values.
The calculated values are actually dependent on input values. I have to populate these values before the object gets referred. So I am just checking if there are any hooks provided by Jackson so that I can do my calculations there. For example JAXB provides afterUnmarshal method to customize the unmarshaling behavior:
void afterUnmarshal(Unmarshaller u, Object parent)
But I could not find similar information about customizing Jackson. Are any such framework hooks provided by Jackson to customize the unmarshaling behavior?
I'd rather recommend to keep your model objects immutable by using constructor creators. That is, all the JSON values are passed to a constructor which would initialize the other calculated properties.
Anyway, if you want to customize an object after deserialization (without writing a deserializer for every type) you can modify the deserializer in a way that at the end it calls a special method(s) of a newly constructed instance. Here is an example which would work for all the classes that implements a special interface (one can consider using an annotation to mark the post construct methods).
public class JacksonPostConstruct {
public static interface PostConstructor {
void postConstruct();
}
public static class Bean implements PostConstructor {
private final String field;
#JsonCreator
public Bean(#JsonProperty("field") String field) {
this.field = field;
}
public void postConstruct() {
System.out.println("Post construct: " + toString());
}
#Override
public String toString() {
return "Bean{" +
"field='" + field + '\'' +
'}';
}
}
private static class PostConstructDeserializer extends DelegatingDeserializer {
private final JsonDeserializer<?> deserializer;
public PostConstructDeserializer(JsonDeserializer<?> deserializer) {
super(deserializer);
this.deserializer = deserializer;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return deserializer;
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Object result = _delegatee.deserialize(jp, ctxt);
if (result instanceof PostConstructor) {
((PostConstructor) result).postConstruct();
}
return result;
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new PostConstructDeserializer(deserializer);
}
});
mapper.registerModule(module);
String json = "{\"field\":\"value\"}";
System.out.println(mapper.readValue(json, Bean.class));
}
}
Output:
Post construct: Bean{field='value'}
Bean{field='value'}
Let's assume that your JSON looks like this:
{
"input1" : "Input value",
"input2" : 3
}
And your POJO class looks like this:
class Entity {
private String input1;
private int input2;
private String calculated1;
private long calculated2;
...
}
In this case you can write a custom deserializer for your Entity class:
class EntityJsonDeserializer extends JsonDeserializer<Entity> {
#Override
public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
InnerEntity innerEntity = jp.readValueAs(InnerEntity.class);
Entity entity = new Entity();
entity.setInput1(innerEntity.input1);
entity.setInput2(innerEntity.input2);
entity.recalculate();
return entity;
}
public static class InnerEntity {
public String input1;
public int input2;
}
}
In above class you can see that Entity has a recalculate method. It could look like this:
public void recalculate() {
calculated1 = input1 + input2;
calculated2 = input1.length() + input2;
}
You can also move this logic to your deserializer class.
Now, you have to inform Jackson that you want to use your custom deserializer:
#JsonDeserialize(using = EntityJsonDeserializer.class)
class Entity {
...
}
The example below shows how to use these classes:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, Entity.class));
This program prints:
Entity [input1=Input value, input2=3, calculated1=Input value3, calculated2=14]

Categories