Jackson serialization to JSON has some nice features like SerializationFeature.WRITE_NULL_MAP_VALUES which allows you to avoid writing nulls into your serialized object.
However, if I use a custom JsonSerializer this setting is ignored and I'm forced to put null checks in all my calls to writeStringField. For example, I register a custom JsonSerializer:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(CloudDocument.class, new ImportProductSerializer());
objectMapper.registerModule(simpleModule);
and then inside of ImportProductSerializer I'm stuck with putting a null check for every field I want to serialize which gets ugly and tedious:
private class ImportProductSerializer extends JsonSerializer<CloudDocument> {
#Override
public void serialize(CloudDocument value,
JsonGenerator gen, //Customize this?
SerializerProvider serializers) throws Exception {
gen.writeStartObject();
gen.writeStringField("type", "add");
//Null check here and every other field: gross!
if(StringUtils.hasText(importProduct.getExtraListingSku1())) {
gen.writeStringField("extralistingsku1", importProduct.getExtraListingSku1());
}
gen.writeEndObject();
}
}
The solution I would like to employ is having my custom JsonSerializer use a custom JsonGenerator. The serialize method takes JsonGenerator in as a parameter and I could easily define it like so:
private class CustomGenerator extends JsonGenerator {
#Override
public JsonGenerator setCodec(ObjectCodec oc) {
super.setCodec(oc);
}
#Override
public void writeStringField(String fieldName, String value) throws IOException {
if(value==null) return;
super.writeStringField(fieldName.toLowerCase(), value);
}
.... //other methods implemented
}
but I'm not sure how to configure the ObjectMapper to use my implementation of CustomGenerator. The benefit of this approach is I could customize the writeField methods to do the null checks and to make sure that the field names subscribe to the naming conventions that are expected which, in this case, involve converting camelCase to lower_underscore.
Is there a way to configure the JsonGenerator that is instantiated with the call to objectMapper.writeValue()?
The question is bit open-ended, as I do not know exactly what you have tried so far; how much of the work is done by custom serializer.
In general serializers are expected to delegate much of the handling to minimize amount of work they do.
But if you have defined a custom serializer to handle POJOs with included fields (instead of a custom serializer for something serialized as a JSON String, or other scalar values), yes, you do need to handle your own checks. This is partly because there are many ways to potentially change inclusion; not just SerializationFeature, but also #JsonFormat annotation for properties; there is no way to handle this transparently when delegating control to custom serializers.
So, yes, if you end up using writeStringField(), you do need to check for default inclusion settings and omit write appropriately.
Related
For example, if I declare a Map<String,Object> with a mixed type value set and I try to serialize with a custom TypeResolverBuilder, I don't have access to the run time type of the value.
But if I want to write type information for some of the values and not others, how can that be accomplished? I've been reading into the DefaultSerializerProvider and it seems to ignore the run time type and just use the JavaType (which is Object.class)
We're using Jackson 2.9
If you are able to edit your POJOs where you need to control the output you should be able to extend JsonSerializer and only write the information you want in the output of the serialize method.
You could also be able to annotate your map to provide a custom serializer and interogate the object to be serialized using instanceof
#JsonSerialize(keyUsing = SpecialSerializer.class)
Map<String, Object> map;
And then in your serializer:
#Override
public void serialize(Object value,
JsonGenerator gen,
SerializerProvider serializers)
throws IOException, JsonProcessingException {
if(value instanceof MySpecialObject){
//special logic here
}
}
Getting exception below while using Jackson api.See attached image.
class BlogSwiftJSONUtil {
static String parseToJSON(Object object){
ObjectMapper objectMapper = new ObjectMapper()
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object)
}
}
Line below I have used to parse output json on all actions.
render contentType: 'application/json', text:BlogSwiftJSONUtil.parseToJSON(listAllResources(params))
Added jackson library in BuildConfig.groovy is as below:
dependencies {
// specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes e.g.
// runtime 'mysql:mysql-connector-java:5.1.29'
// runtime 'org.postgresql:postgresql:9.3-1101-jdbc41'
test "org.grails:grails-datastore-test-support:1.0.2-grails-2.4"
runtime 'com.fasterxml.jackson.core:jackson-core:2.0.4'
runtime 'com.fasterxml.jackson.core:jackson-databind:2.0.4'
runtime 'com.fasterxml.jackson.core:jackson-annotations:2.0.4'
}
Anyone why am I getting this exception.
Below are some findings from me:
if I pass map like object.properties rather than object itself it works.
It seems that it's also trying to serialize validation errors as well.
Any helps would be worth.
Please let me know if I could share any other details.
Thanks!
Inorder for jackson to marshall your response, you need a bean which has private field with public getter/setter or define a field with public visibility. From the screenshot you pasted, it seems somehow your api call failed which redirected for spring to handle the exception which jackson couldn't serialize.
You need to overcome this by adding following:
objectMapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
As you are using it for rest-api and would most probably serialise domains, enums and some custom read-only pojos. This issue is because of failure in serialising the validation errors which are injected to domains. You may customise domains to choose fields for serialisations and deserialisations.
See this.
To be more flexible manually add your own serialiser and give your own definitions as below:
Below is the way to add a custom serialiser
import com.blog.swift.marshaller.JacksonSerializer
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
class JSONUtil{
static String parsetoJSON(Object object){
ObjectMapper objectMapper = new ObjectMapper()
Module testModule = new SimpleModule()
testModule.addSerializer(new JacksonSerializer(object.getClass()));
objectMapper.registerModule(testModule)
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object)
}
}
Below is a sample custom serialiser.
class JacksonSerializer extends StdSerializer{
protected BSJacksonSerializer(Class t) {
super(t)
}
#Override
void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonGenerationException {
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("test","test")
jsonGenerator.writeEndObject()
}
}
StdSerializer is an abstract class which gives base implementations to help leverage focus on custom serialisation logic than exception handling and any other thing.
Hope it helps!
When deserializing a variety of JSON messages, I want to provide a default value for attributes of a certain type. It is generally suggested to simply specify the value in the Class, but this is error-prone if you have to do this across many Classes. You might forget one and end up with null instead of a default value. My intention is to set every property that is an Optional<T> to Optional.absent. Since null is exactly what Optional is trying to eliminate, using them with Jackson has proven to be frustrating.
Most features of Jackson that allow you to customize the deserialization process focus on the JSON that is the input, not around the process of instantiating the Object that you are deserializing into. The closest I seem to be getting to a general solution is by building my own ValueInstantiator, but there are two remaining issues I have:
how do I make it only instantiate Optional as absent but not interfere with the rest of the instantiation process?
how do I wire the end result into my ObjectMapper?
UPDATE: I want to clarify that I am looking for a solution that does not involve modifying each Class that contains Optional's. I'm opposed to violating the DRY principle. Me or my colleagues should not have to think about having to do something extra every time we add Optional's to a new or existing Class. I want to be able to say, "make every Optional field in every Class I deserialize into, pre-filled with Absent", only once, and be done with it.
That means the following are out:
abstract parent class (need to declare)
custom Builder/Creator/JsonDeserializer (needs annotation on each applicable class)
MixIn's? I tried this, combined with reflection, but I don't know how to access the Class I'm being mixed into...
Specifically for java.lang.Optional, there is a module by the Jackson guys themselves: https://github.com/FasterXML/jackson-datatype-jdk8
Guava Optional is covered by https://github.com/FasterXML/jackson-datatype-guava
It will create a Optional.absent for null's, but not for absent JSON values :-(.
See https://github.com/FasterXML/jackson-databind/issues/618 and https://github.com/FasterXML/jackson-datatype-jdk8/issues/2.
So you're stuck with initializing your Optionals just as you should initialize collections. It is a good practice, so you should be able to enforce it.
private Optional<Xxx> xxx = Optional.absent();
private List<Yyy> yyys = Lists.newArrayList();
You can write a custom deserializer to handle the default value. Effectively you will extend the appropriate deserializer for the type of object you are deserializing, get the deserialized value, and if it's null just return the appropriate default value.
Here's a quick way to do it with Strings:
public class DefaultStringModule extends SimpleModule {
private static final String NAME = "DefaultStringModule";
private static final String DEFAULT_VALUE = "[DEFAULT]";
public DefaultStringModule() {
super(NAME, ModuleVersion.instance.version());
addDeserializer(String.class, new DefaultStringDeserializer());
}
private static class DefaultStringDeserializer extends StdScalarDeserializer<String> {
public DefaultStringDeserializer() {
super(String.class);
}
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
String deserialized = jsonParser.getValueAsString();
// Use a default value instead of null
return deserialized == null ? DEFAULT_VALUE : deserialized;
}
#Override
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
return deserialize(jp, ctxt);
}
}
}
To use this with an ObjectMapper you can register the module on the instance:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new DefaultStringModule());
To handle default values for fields not present in the JSON, I've typically seen this done through the use of a builder class that will construct the class using the values supplied and add any default values for the missing fields. Then, on the deserialized class (e.g. MyClass), add a #JsonDeserialize(builder = MyClass.Builder.class) annotation to indicate to Jackson to deserialize MyClass by way of the builder class.
Your value object should initialize these values as absent. That's the way to ensure that default values have no nulls. Guava module's Optional handler really should only deserialize them as "absent" (even with explicit JSON nulls), and never as nulls, with later versions.
But since Jackson only operates on JSON properties that exist (and not on things that could exist but do not), POJO still needs to have default absent assignment.
I am using Jackson 2.3.2 library in a Spring MVC project and trying to specify a custom JSON serialization on a custom wrapper Object by implementing the JSONSerializableWithType interface which includes a method called serialize() that Jackson calls, when trying to serialize an Object instance to JSON.
Strangely this serialize method is called correctly as long as my Object wrapper does not extend LinkedHashMap<...>.
If my Object wrapper extends a Class that Jackson "knows" how to serialize in a default way, the serialize() method is not called anymore.
My wrapper class looks like this:
public class ResponseRoomOccupancy
extends LinkedHashMap<...>
implements org.codehaus.jackson.map.JsonSerializableWithType {
#Override
public void serialize(JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
// actual serialization of the object
jgen.writeEndObject();
}
#Override
public void serializeWithType(JsonGenerator jgen,
SerializerProvider provider, TypeSerializer typeSer)
throws IOException, JsonProcessingException {
serialize(jgen, provider);
}
}
The controller:
#RequestMapping(value = "/occupancy")
public #ResponseBody ResponseRoomOccupancy
getRoomOccupancy(RequestRoomOccupancy request) {
return appointmentService.getEnrichedRoomOccupancy(request);
}
Can anybody explain to me why Jackson does not use the custom-defined serialize method on a class that extends LinkedHashMap?
UPDATE:
As Sotirios Delimanolis pointed out correctly, the JSONSerializableWithType interface is part of an older version of Jackson (<1.9).
However I dont know why this kind of custom serialization works with custom defined classes (e.g. if LinkedHashMap is exchanged with some class Foo<...>).
You're using Jackson 2 which is completely incompatible with Jackson 1. JsonSerializableWithType is an interface from Jackson 1. You can't have them work together. Jackson 2 simply doesn't look for JsonSerializableWithType.
Instead, annotate your LinkedHashMap type with
#JsonSerialize(using = YourSerializer.class)
and have YourSerializer do the work.
Regarding your comment and edit, Jackson has some default serializers/deserializers for known types, like List, Set, String, Number, and Map. However, it does not know your custom types. It must build a new serializer based on what it finds from analyzing your type.
I am using the Jackson library to write custom serializers, and register them inside of a custom ObjectMapper. However, I also want to change the default serialization to simply output the string representation of the object when a more specific custom serialization has not been written.
For example, say I have written custom serializers for classes "Map" and "Entry", in addition to the default serializer. Then the serialization module within my custom ObjectMapper might look like this:
SimpleModule module = new SimpleModule("module", new Version(0, 1, 0, "alpha", null, null));
module.addSerializer(Entry.class, new EntryJsonSerializer());
module.addSerializer(Map.class, new MapJsonSerializer());
module.addSerializer(Object.class, new DefaultJsonSerializer());
this.registerModule(module);
However, I'm finding that the module will use DefaultJsonSerializer to serialize Map and Entry objects (as they are also Object objects).
How can I change the default serialization behavior, while ensuring Entry and Map objects are serialized as intended?
The problem is probably that the actual type of values (say, String) is used for locating serializers.
One solution would be to register serializers for value types, if you know them.
Alternatively, you could force use of static typing; this would make serializer lookup use declared (static) type, not actual runtime type.
This can be done with:
objectMapper.enable(MapperFeature.USE_STATIC_TYPING);
I've gotten around the problem by writing a single serializer and using a series of if statements to implement prioritization:
public final class UnifiedJsonSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object object, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (object instanceof Entry) {
// Entry serialization code
} else if (object instanceof Map) {
// Map serialization code
} else {
// default serialization code
}
}