Let's suppose I have an enum Status that looks like this.
public enum Status {
SUCCESS,
FAIL,
RETRY,
UNKNOWN
}
I am receiving status property from a JSON that could look like following examples.
{"status":"success"} // valid case, deserialize to Status.SUCCESS
{"status":"fail"} // valid case, deserialize to Status.FAIL
{"status":"retry"} // valid case, deserialize to Status.RETRY
But any other value should be deserialized to Status.UNKNOWN. Examples.
{"status":"blabla"} // invalid case, deserialize to Status.UNKNOWN
{"status":"John"} // invalid case, deserialize to Status.UNKNOWN
I know I could do it by writing a custom deserializer, but I'd try to avoid that because i have many, many enums in my program, and requiring a custom deserializer for each of them would be an overkill.
Ideally, some kind of constructor from regex that matches any string (except for the "success", "fail" and "retry").
Is there a way to do it with Jackson without writing custom deserializer?
If all of your enums have UNKNOWN value, you can write one custom deserializer like this:
class EnumDeserializer extends JsonDeserializer<Enum> {
private final Class<? extends Enum> enumType;
public EnumDeserializer(Class<? extends Enum> enumType) {
this.enumType = enumType;
}
#Override
public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
try {
String stringValue = jp.getValueAsString().toUpperCase();
return Enum.valueOf(enumType, stringValue.toUpperCase());
} catch (IllegalArgumentException e) {
return Enum.valueOf(enumType, "UNKNOWN");
}
}
}
And configure your mapper to user it:
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
final JavaType type,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new EnumDeserializer((Class<Enum<?>>) type.getRawClass());
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Alternatively you can use jackson deserialization feature for setting default value for unknown enum properties:
enum MyEnum { A, B, #JsonEnumDefaultValue UNKNOWN }
...
final ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
MyEnum value = mapper.readValue("\"foo\"", MyEnum.class);
assertSame(MyEnum.UNKNOWN, value);
But with such approach you'll need to change all your enums to use #JsonEnumDefaultValue annotation for default value, plus by default it doesn't handle lowercase enum values.
Related
I want the ability to serialize a field in an object based on the JsonView. It doesn't have to be JsonView, it's just what I have been exploring. Basically using #JsonView annotation on RestController class, it would serialize my POJO.
However I have a User and Admin view where there is an object:
Map secrets;
That for an Admin view I want both key:value to show up and serialize, but for a User I would only want a List keys or if its simpler, keep Map but only show the key and all of the values switch to '****' 4 asteriks or something.
I thought about having two getters but the JsonView annotation doesn't work like that where two getters can have different views and Jackson knows which one to call.
I'm not sure JsonView is the best thing here. Perhaps a JsonGetter method that serializes based on view or some custom serializer, but I think there might be a more straightforward way to do it with Jackson and few annotations
What I am looking to do is:
Person.java
Map<String,String> secrets;
This would serialize to (for Admin):
{
"person":{
"secrets":{
"password":"123456",
"creditCard":"1234 5678 9101"
}
}
}
This would serialize to (for User):
{
"person":{
"secrets":{
"password":"****",
"creditCard":"****"
}
}
}
However what I would envision what I could do is something like
#JsonView(View.User.class)
Map<String,String> getSecrets(){
this.secrets.forEach(value -> "****") //code would be different but basically setting all values to ****
return secrets;
}
#JsonView(View.Admin.class)
Map<String,String> getSecrets(){
//Returning secrets as they should be
return secrets;
}
You can try defining a custom serializer for the object mapper , so that whenever the object mapper is used for serialization you can check and convert the password and credit card field to the value you choose.For example
public class ItemSerializer extends StdSerializer<Item> {
public ItemSerializer() {
this(null);
}
public ItemSerializer(Class<Item> t) {
super(t);
}
#Override
public void serialize(
Item value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeStringField("itemName", value.itemName);
jgen.writeNumberField("owner", value.owner.id);
jgen.writeEndObject();
}
}
You can provide an object mapper that utilizes this custom serializer then,
Item myItem = new Item(1, "theItem", new User(2, "theUser"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
In your case you can register the objectmapper bean with the custom serializer in the spring context and make jackson use your object mapper bean.
Or using #JsonSerialize annotation like :
public class Event {
public String name;
#JsonSerialize(using = CustomDateSerializer.class)
public Date eventDate;
}
Public class CustomDateSerializer extends StdSerializer<Date> {
private static SimpleDateFormat formatter
= new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
public CustomDateSerializer() {
this(null);
}
public CustomDateSerializer(Class<Date> t) {
super(t);
}
#Override
public void serialize(
Date value, JsonGenerator gen, SerializerProvider arg2)
throws IOException, JsonProcessingException {
gen.writeString(formatter.format(value));
}
}
Refer:
https://www.baeldung.com/jackson-json-view-annotation
https://www.baeldung.com/jackson-custom-serialization
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 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.
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:
Deserialize to String or Object using Jackson
#Valid when creating objects with jackson without controller
Jackson custom serialization and deserialization
Is there an option in Jackson to let the deserialization fail when it encounters a null for any (non-primitive) object property or for any (non-primitive) array element?
It should work similarly to the Deserialization Feature - FAIL_ON_NULL_FOR_PRIMITIVES).
Example #1
So deserializing {"name": null} should throw an exception when deserializing into a POJO
class User {
private String name = "Default name";
//+setters/getters
}
However, it should work fine and throw no exception when deserializing {} into that POJO, as the default value for the name field is not overwritten (see comments).
Example #2
I would like to avoid null elements in arrays also, so deserializing ["A", "B", null] should throw an exception when deserializing into List<String>.
There is no easy way to do this as far as I know (jackson-databind 2.4.2).
I suggest you take a look at using custom constructors / factory methods for creating objects out of Json. That allows you to do more advanced validation of incoming Json strings.
Solution to example #1
You can add this feature by registering a SimpleModule with an added BeanDeserializerModifier in order to alter the deserialization functionality.
By overriding the appropriate method you can use the default JsonDeserializer to deserialize the object easily and throw a mapping exception if a null property occurs.
You can find details in the answers of a similar SO question.
Extend the existing deserialization:
//instantiate an objectMapper and alter the deserialization functionality
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
return new DisallowNullDeserializer(beanDesc.getBeanClass(), deserializer);
}
});
mapper.registerModule(simpleModule);
The actual deserialization and exception throwing is happening in this utility class:
public class DisallowNullDeserializer<T> extends StdDeserializer<T> implements ResolvableDeserializer {
private final JsonDeserializer<?> delegateDeserializer;
public DisallowNullDeserializer(Class<T> clazz, JsonDeserializer<?> delegateDeserializer) {
super(clazz);
this.delegateDeserializer = delegateDeserializer;
}
#Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// delegate actual deserialization to default deserializer
T out = (T) delegateDeserializer.deserialize(jp, ctxt);
// check for null properties & throw exception
// -> there may be a better, more performant way to find null properties
Map<String, Object> propertyMap = mapper.convertValue(out, Map.class);
for (Object property: propertyMap.values())
if (property == null)
throw ctxt.mappingException("Can not map JSON null values.");
return out;
}
// there is no obvious reason why this is needed; see linked SO answers
#Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) delegateDeserializer).resolve(ctxt);
}
}
Is there a way by which Jackson allows custom serialization for a specific type only in a particular class?
Here is my scenario:
I have ClassA.java which is something like:
public Class ClassA {
byte[] token;
String name;
public getToken() {
return token;
}
public setToken(byte[] newToken) {
token = newToken;
}
public getName() {
return name;
}
public setName(String newName) {
name = newName;
}
}
I do not have access to this class as it is in an external jar. However, I want to serialize the token byte array here in a particular manner. I have created a Custom serializer that does that and tries adding it to the mapper in all the ways mentioned in Jackson docs.
public class ByteArrayJacksonSerializer extends JsonSerializer<byte[]> {
public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String token = doMyThing(value);
jgen.writeString(token);
}
}
And in mapper, something like this:
public class MyObjectMapper extends ObjectMapper {
CustomSerializerFactory sf = new CustomSerializerFactory();
sf.addGenericMapping(byte[].class, new ByteArrayJacksonSerializer());
this.setSerializerFactory(sf);
and some more code here...
}
However I can do it only for byte[] in general, and not for ONLY byte[] in ClassA. Is there a way to let Jackson know that this custom serializer must be used ONLY for fields of byte[] type in ClassA, and to do serialization it's own way for all other classes?
You should use MixIn feature. In your example you have to create new interface:
interface ClassAMixIn {
#JsonSerialize(using = ByteArrayJacksonSerializer.class)
byte[] getToken();
}
which specifies custom serializer for given property. Now we have to configure ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(ClassA.class, ClassAMixIn.class);
Your custom serializer will be used only for serializing byte array property in ClassA.
I have a a map that looks like this:
public class VerbResult {
#JsonProperty("similarVerbs")
private Map<Verb, List<Verb>> similarVerbs;
}
My verb class looks like this:
public class Verb extends Word {
#JsonCreator
public Verb(#JsonProperty("start") int start, #JsonProperty("length") int length,
#JsonProperty("type") String type, #JsonProperty("value") VerbInfo value) {
super(length, length, type, value);
}
//...
}
I want to serialize and deserialize instances of my VerbResult class, but when I do I get this error: Can not find a (Map) Key deserializer for type [simple type, class my.package.Verb]
I read online that you need to tell Jackson how to deserialize map keys, but I didn't find any information explaining how to go about doing this. The verb class needs to be serialized and deserialzed outside of the map as well, so any solution should preserve this functionality.
Thank you for your help.
After a day of searching, I came across a simpler way of doing it based on this question. The solution was to add the #JsonDeserialize(keyUsing = YourCustomDeserializer.class) annotation to the map. Then implement your custom deserializer by extending KeyDeserializer and override the deserializeKey method. The method will be called with the string key and you can use the string to build the real object, or even fetch an existing one from the database.
So first in the map declaration:
#JsonDeserialize(keyUsing = MyCustomDeserializer.class)
private Map<Verb, List<Verb>> similarVerbs;
Then create the deserializer that will be called with the string key.
public class MyCustomDeserializer extends KeyDeserializer {
#Override
public MyMapKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
//Use the string key here to return a real map key object
return mapKey;
}
}
Works with Jersey and Jackson 2.x
As mentioned above the trick is that you need a key deserializer (this caught me out as well). In my case a non-String map key was configured on my class but it wasn't in the JSON I was parsing so an extremely simple solution worked for me (simply returning null in the key deserializer).
public class ExampleClassKeyDeserializer extends KeyDeserializer
{
#Override
public Object deserializeKey( final String key,
final DeserializationContext ctxt )
throws IOException, JsonProcessingException
{
return null;
}
}
public class ExampleJacksonModule extends SimpleModule
{
public ExampleJacksonModule()
{
addKeyDeserializer(
ExampleClass.class,
new ExampleClassKeyDeserializer() );
}
}
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ExampleJacksonModule() );
Building on the answer given here that suggests to implement a Module with a deserializer. The JodaTime Module is an easy to understand full example of a module containing serializers and deserializers.
Please note that the Module feature was introduced in Jackson version 1.7 so you might need to upgrade.
So step by step:
create a module containing a (de)serializer for your class based on the Joda example
register that module with mapper.registerModule(module);
and you'll be all set
Assuming we have a Map property, like the following:
class MyDTO{
#JsonSerialize(keyUsing = MyObjectKeySerializer.class)
#JsonDeserialize(keyUsing = MyObjectKeyDeserilazer.class)
private Map<MyObjectKey , List<?>> map;
}
We serilize the MyObjectKey as a json string, while call objectMapper.writeAsString;
And deserilize from the json string,to MyObjectKey
public class MyObjectKeySerializer extends StdSerializer<MyObjectKey> {
public Serializer() {
super(MyObjectKey.class);
}
#Override
public void serialize(MyObjectKey value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeFieldName(JsonUtil.toJSONString(value));
}
}
public class MyObjectKeyDeserializer extends KeyDeserializer {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return JsonUtil.toObject(key, MyObjectKey.class);
}
}
After scouring the web, I think I have a decent solution for how to handle POJO-style keys (although, as always, you are best served not using a full object as a map key).
Serializer (registered as a Jackson module, inside of Spring Boot):
#Bean
fun addKeySerializer(): Module =
SimpleModule().addKeySerializer(YourClass::class.java, YourClassSerializer())
class YourClassSerializer() : JsonSerializer<YourClass>() {
override fun serialize(value: DataElement, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeFieldName(jacksonObjectMapper().writeValueAsString(value))
}
}
(note that, in a standard Java environment, you will have to instantiate your own objectMapper instance here)
Deserializer:
#Bean
fun addKeyDeserializer(): Module =
SimpleModule().addKeyDeserializer(YourClass::class.java, YourClassDeserializer())
class YourClassDeserializer() : KeyDeserializer() {
override fun deserializeKey(key: String, ctxt: DeserializationContext): YourClass? {
return ctxt.parser.readValueAs(YourClass::class.java)
}
}