How do you override the null serializer in Jackson 2.0? - java

I'm using Jackson for JSON serialization, and I would like to override the null serializer -- specifically, so that null values are serialized as empty strings in JSON rather than the string "null".
All of the documentation and examples I've found on how to set null serializers refers to Jackson 1.x -- for example, the code at the bottom of http://wiki.fasterxml.com/JacksonHowToCustomSerializers no longer compiles with Jackson 2.0 because StdSerializerProvider no longer exists in the library. That web page describes Jackson 2.0's module interface, but the module interface has no obvious way to override the null serializer.
Can anyone provide a pointer on how to override the null serializer in Jackson 2.0?

Override the JsonSerializer serialize method as below.
public class NullSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
// any JSON value you want...
jgen.writeString("");
}
}
then you can set NullSerializer as default for custom object mapper:
public class CustomJacksonObjectMapper extends ObjectMapper {
public CustomJacksonObjectMapper() {
super();
DefaultSerializerProvider.Impl sp = new DefaultSerializerProvider.Impl();
sp.setNullValueSerializer(new NullSerializer());
this.setSerializerProvider(sp);
}
}
or specify it for some property using #JsonSerialize annotation, e.g:
public class MyClass {
#JsonSerialize(nullsUsing = NullSerializer.class)
private String property;
}

I was not able to get the accepted answer to work for me. Perhaps because my ObjectMapper is a Spring Bean in my environment.
I reverted by to using a SimpleModule.
Same serializer:
public class NullSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
// any JSON value you want...
jgen.writeString("");
}
}
The annotation is located in a Mixin as I don't have access to modifying MyClass:
public abstract class MyClassMixin {
#JsonSerialize(nullsUsing = NullSerializer.class)
public String property;
}
To attach the serializer to my mapper, I use a module in my Spring component:
#AutoWired
ObjectMapper objectMapper;
#PostConstruct
public void onPostConstruct() {
SimpleModule module = new SimpleModule();
module.setMixInAnnotation(MyClass.class, MyClassMixin.class);
objectMapper.registerModule(module);
}

Related

How do I serialize using two different getters based on JsonView in RestController?

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

Spring Boot custom serializer for Collection class

I was trying to implement a custom serializer for one of the properties of my object to get a different JSON structure when I return it from my REST controller.
My constraints are I cannot change the interface of the REST controller or the model classes (so I cannot add extra annotation etc, that would maybe make this easier). The only thing I could think of, making it render different than described in the model is a custom serializer, if there are any better approaches for this, please don't hesitate to tell me a different approach that is within the constraints.
My models look something like this:
public class WrapperModel {
// a lot of autogenerated fields
List<Property> properties;
// getters/setters
}
public class Property {
private String name;
private String value;
// getters / setters
}
So when this is rendered is looks like so:
{ ....
"properties": [
{"key1": "value1"}, {"key2": "value2"},...
]
}
What I would want is this:
{ ....
"properties": {
"key1": "value1",
"key2": "value2",
...
}
}
The serializer for this is easy enough:
public class PropertyListJSONSerializer extends StdSerializer<List<Property>> {
//....
#Override
public void serialize(List<Property> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
for(Property p: value){
gen.writeStringField(p.getName(), p.getValue());
}
gen.writeEndObject();
}
}
Now when I try to register this serializer inside a #Configuration file:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
SimpleModule module = new SimpleModule();
module.addSerializer(List<Property>.class, new PropertyListJSONSerializer());
mapper.registerModule(module);
return mapper;
}
this doesn't work, because List<Property>.class cannot be used for addSerializer since it's a template class. Is there any other way to add this serializer or something that does something similar?
I do not want to add a custom serializer for WrapperModel since this class is autogenerated and fields can be added and removed. This should be possible without modifying the application code (if I had a custom serializer you would need to add/remove the fields from the serializer also(?)). Or is there a way to just use the Standard serializer for the class and just manually handle this one List<> field.
The model classes are generated by the Spring Boot openapi code generator, so there is a very limited set of JSON annotations I can put on top of the model fields (if there's an annotation way, please dont hesitate to post as I can check in the openapi sourcecode if that particular annotation is supported). But I would rather go with either a custom serializer for List<Property> if that is at all possible or writing a serializer for WrapperModel that uses StdSerializer for everything and only handle the List property myself.
MixIn
In that case we need to use MixIn feature. Create interface like below:
interface WrapperModelMixIn {
#JsonSerialize(using = PropertyListJSONSerializer.class)
List<Property> getProperties();
}
and register it like below:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(WrapperModel.class, WrapperModelMixIn.class);
Older proposal
You need to use Jackson types which allow to register serialiser for generic type. Your serialiser after change could look like below:
class PropertyListJSONSerializer extends StdSerializer<List<Property>> {
public PropertyListJSONSerializer(JavaType type) {
super(type);
}
#Override
public void serialize(List<Property> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();
for (Property p : value) {
gen.writeStringField(p.getName(), p.getValue());
}
gen.writeEndObject();
}
}
And you can register it as below:
ObjectMapper mapper = new ObjectMapper();
CollectionType propertiesListType = mapper.getTypeFactory().constructCollectionType(List.class, Property.class);
SimpleModule module = new SimpleModule();
module.addSerializer(new PropertyListJSONSerializer(propertiesListType));
mapper.registerModule(module);

How to serialize boolean as String in Spring Boot Rest response?

Recently I have migrated the project from Jersey to Spring Rest. Previously boolean field was serialized as String like success: "true", now it became without quotes success: true. It wouldn't be a problem but old apps rely on it and can't deserialize. How to return boolean values as String in Spring Boot? Any spring.jackson.serialization property?
If you don't have access to this field, create a class which will do serialization for it:
public class StringBooleanSerializer extends JsonSerializer<Boolean> {
#Override
public void serialize(Boolean bool, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(bool ? "true" : "false");
}
}
Register it with your object mapper:
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(new StringBooleanSerializer());
objectMapper.registerModule(simpleModule);
That's it. However if you have access to this field you can do just this:
#JsonSerialize(using=StringBooleanSerializer.class)
private Boolean bool;
The same goes with deserialization if necessary.
Write your custom serializer and deserializer and use following annotations on that boolean field:
#JsonSerialize and #JsonDeserialize.

Custom Jackson Serializer for a specific type in a particular class

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.

Deserializing non-string map keys with Jackson

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)
}
}

Categories