I need to deserialize a long and complex json for which I wrote a set of java classes to map the data, and I had to write custom deserializers for many fields of different types (including String, Boolean, BigDecimal, etc.) .
I know I can annotate all fields in the java classes with the corresponding custom deserializer (like below), but then I would need to annotate almost all the fields in all the classes.
@JsonDeserialize(using = CustomBooleanJsonDeserializer.class)
private boolean active;
I also know that I can register a module in the Spring default ObjectMapper
(like here ), but I just want to use these custom deserializers for these specific classes.
@Bean
public Module customDeserializersModule() {
SimpleModule module = new SimpleModule();
module.addDeserializer(Boolean.class, new CustomBooleanJsonDeserializer());
// add other custom deserializers
return module;
}
I even know that I can use a custom ObjectMapper
in the RestController
, but I don't want to give up the convenience of automatic data binding via @RequestBody
, because I must prevent others from using this without the necessary custom deserializers.
@RequestMapping(method = RequestMethod.POST, value = "/data")
public ResponseEntity<ServerInfo> register(@RequestBody DataMapper data) {
// DataMapper is the target POJO class of the json's deserialization
}
In short, I'm looking for something like this at class level:
@JsonDeserialize(using = CustomStringJsonDeserializer.class, forType = String.class)
@JsonDeserialize(using = CustomBooleanJsonDeserializer.class, forType = Boolean.class)
@JsonDeserialize(using = CustomBigDecimalJsonDeserializer.class, forType = BigDecimal.class)
public class DataMapper implements Serializable {
// obviously, @JsonDeserialize doesn't have a forType method
}
or maybe some way to implement a custom deserializer for the DataMapper
class, that defines how to deserialize each field according to its data type (without having to annotate each field):
@JsonDeserialize(using = DataMapperJsonDeserializer.class)
public class DataMapper implements Serializable {
// How can I implement the DataMapperJsonDeserializer with these
// characteristics? I know about the ContextualDeserializer interface,
// but I don't know how to use it without annotating each field.
}
or some way of restricting the effect of a module to just one package or set of classes :
module.restrictedTo(/*some package or set of classes*/);
// com.fasterxml.jackson.databind.Module doesn't have a restrictedTo method
You can try to use SimpleModule
together with ContextualDeserializer
interface. First can be used for wrapping default deserialiser and second for checking type configuration - checking annotation.
Let's start from annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ForceCustomDeserializer {
}
I assume that you have only one custom implementation for given type but in case it is not true extend above annotation and provide some extra info which allow to use proper deserialisers. For example, below we can see two custom deserialisers which extra logs some info and run default deserialisation. Base deserialiser is used because in case you have some extra configuration we do not loose it.
class CustomBoolDeserializer extends StdScalarDeserializer<Boolean> implements ContextualDeserializer {
private NumberDeserializers.BooleanDeserializer base;
public CustomBoolDeserializer(NumberDeserializers.BooleanDeserializer base) {
super(Boolean.class);
this.base = base;
}
@Override
public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
System.out.println("Custom BooleanDeserializer ....");
return base.deserialize(p, ctxt);
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
Class<?> parent = property.getMember().getDeclaringClass();
ForceCustomDeserializer annotation = parent.getAnnotation(ForceCustomDeserializer.class);
return annotation == null ? base : this;
}
}
class CustomStringDeserializer extends StringDeserializer implements ContextualDeserializer {
private final StringDeserializer base;
public CustomStringDeserializer(StringDeserializer base) {
this.base = base;
}
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
System.out.println("Custom StringDeserializer ....");
return base.deserialize(p, ctxt);
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
Class<?> parent = property.getMember().getDeclaringClass();
ForceCustomDeserializer annotation = parent.getAnnotation(ForceCustomDeserializer.class);
return annotation == null ? base : this;
}
}
We can test above custom implementations as below:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule forcedCustomModule = new SimpleModule();
forcedCustomModule.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (deserializer instanceof StringDeserializer) {
// wrap with yours or return new deserializer
return new CustomStringDeserializer((StringDeserializer) deserializer);
}
if (deserializer instanceof NumberDeserializers.BooleanDeserializer) {
// wrap with yours or return new deserializer
return new CustomBoolDeserializer((NumberDeserializers.BooleanDeserializer) deserializer);
}
// override for other types
return deserializer;
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(forcedCustomModule);
System.out.println(mapper.readValue(jsonFile, Pojo.class));
}
}
@ForceCustomDeserializer
class Pojo {
private String name;
private boolean bool;
// getters, setters, toString
}
Above example for below JSON
payload:
{
"name": "Jackson",
"bool": true
}
prints:
Custom StringDeserializer ....
Custom BooleanDeserializer ....
Pojo{name='Jackson', bool=true}
See also:
You can define a custom deserializer for the class ( as the second idea in the question ) and use your own custom ObjectMapper
inside:
public class DataMapperJsonDeserializer extends JsonDeserializer<DataMapper> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
static {
SimpleModule module = new SimpleModule();
module.addDeserializer(BigInteger.class, new CustomBigIntegerJsonDeserializer());
module.addDeserializer(BigDecimal.class, new CustomBigDecimalJsonDeserializer());
module.addDeserializer(Boolean.class, new CustomBooleanJsonDeserializer());
module.addDeserializer(String.class, new CustomStringJsonDeserializer());
objectMapper.registerModule(module);
objectMapper.addMixIn(DataMapper.class, DefaultJsonDeserializer.class);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setDateFormat(simpleDateFormat);
}
@Override
public DataMapper deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return objectMapper.readValue(jsonParser, DataMapper.class);
}
@JsonDeserialize
private interface DefaultJsonDeserializer {
// Reset default json deserializer
}
}
Note the use of Jackson Mix-in Annotations ( the DefaultJsonDeserializer
interface ) to dynamically remove the custom deserializer from the POJO
class, avoiding the StackOverflowError
that would otherwise be thrown as a result of objectMapper.readValue(jsonParser, DataMapper.class)
.
Then, it's just to annotate the POJO
class:
@JsonDeserialize(using = DataMapperJsonDeserializer.class)
public class DataMapper implements Serializable {
// It is not necessary to annotate each field with custom deserializers.
}
You can even add other POJO
classes as fields of DataMapper
and the custom deserializers for each type will be automatically applied to its fields, without need for annotations.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.