How to invoke JsonSerializer inside another custom JsonSerializer Jackson - java

I have two classes. First with field of second class.
class A {
#JsonSerializer(using = CustomBSerializer.class)
private B b;
}
class B {
...
}
And I have two custom serializers:
class CustomBSerializer extends JsonSerializer<B> {
...
}
class CustomASerializer extends JsonSerializer<A> {
#Override
public void serialize(A a, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
//write here
jgen.writeEndObject();
}
}
What method I should add instead of comment in CustomASerializer to write serialized value of field b with CustomBSerializer?

You can use writeObjectField method. Jackson should use default or custom serializer if exists.
Below example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.Date;
public class Main {
public static void main(String[] args) throws Exception {
B b = new B();
b.setProperty("Value");
A a = new A();
a.setB(b);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(a));
}
}
#JsonSerialize(using = ASerializer.class)
class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
class ASerializer extends JsonSerializer<A> {
#Override
public void serialize(A value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeStringField("now", new Date().toString());
gen.writeObjectField("b", value.getB());
gen.writeEndObject();
}
}
#JsonSerialize(using = BSerializer.class)
class B {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
class BSerializer extends JsonSerializer<B> {
#Override
public void serialize(B value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeStringField("p", value.getProperty());
gen.writeEndObject();
}
}
prints:
{
"now" : "Wed Aug 26 22:27:08 CEST 2015",
"b" : {
"p" : "Value"
}
}

You don't always have access to class code, you will not always have the possibility to modify A or B class to put the annotation #JsonSerialize(using = BSerializer.class). In these cases you will need to call CustomBSerializer in CustomASerializer, which was also what the question was asking in first place. This is the way to do it:
class CustomASerializer extends JsonSerializer<A> {
#Override
public void serialize(A a, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
final ObjectMapper mapper = (ObjectMapper)jgen.getCodec();
SimpleModule module = new SimpleModule("CustomBSerializer");
module.addSerializer(B.class, new CustomBSerializer());
mapper.registerModule(module);
jgen.writeStartObject();
mapper.writeValue(jgen, a.getB());
jgen.writeEndObject();
}
}

Related

Big number deserialisation throws NumberFormatException

I wrote below program to convert Parameter to JsonNode. Getting NumberFormatException when I set 3.9E38. How to set BigDecimal in JsonNode?
public class JsonCheck {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
Parameter p = new Parameter();
p.setPrevValue(new BigDecimal("3.9E38"));
JsonNode node = om.convertValue(p,JsonNode.class);
System.out.println(node);
}
public static class DefaultValueSerializer extends JsonSerializer<BigDecimal> {
#Override
public void serialize(BigDecimal o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(o.toPlainString());
}
}
public static class Parameter {
#JsonSerialize(using = DefaultValueSerializer.class)
private BigDecimal prevValue;
public void setPrevValue(BigDecimal prevValue) {
this.prevValue = prevValue;
}
public BigDecimal getPrevValue() {
return prevValue;
}
}
}
It looks like this because com.fasterxml.jackson.core.JsonParser implementation used during deserialization (in your case conversion) process can not parse really big numbers. By default, number is treated as Long and value 3.9E38 exceeds it's range. Unfortunately, features USE_BIG_DECIMAL_FOR_FLOATS and USE_BIG_INTEGER_FOR_INTS are not handled in this case and we need to write customization here. See below example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.util.JsonParserDelegate;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.math.BigDecimal;
public class JsonApp {
public static void main(String[] args) {
Parameter parameter = new Parameter();
parameter.setPrevValue(new BigDecimal("3.9E38"));
SimpleModule module = new SimpleModule();
module.addDeserializer(JsonNode.class, new BigDecimalFirstJsonNodeDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
JsonNode node = mapper.convertValue(parameter, JsonNode.class);
System.out.println(node);
}
}
class BigDecimalFirstJsonNodeDeserializer extends JsonNodeDeserializer {
#Override
public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(new BigDecimalJsonParser(p), ctxt);
}
}
class BigDecimalJsonParser extends JsonParserDelegate {
public BigDecimalJsonParser(JsonParser parser) {
super(parser);
}
#Override
public NumberType getNumberType() {
return NumberType.BIG_DECIMAL;
}
#Override
public BigDecimal getDecimalValue() throws IOException {
String value = getText();
return new BigDecimal(value);
}
}
class BigDecimalPlainStringJsonSerializer extends JsonSerializer<BigDecimal> {
#Override
public void serialize(BigDecimal o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(o.toPlainString());
}
}
class Parameter {
#JsonSerialize(using = BigDecimalPlainStringJsonSerializer.class)
private BigDecimal prevValue;
public void setPrevValue(BigDecimal prevValue) {
this.prevValue = prevValue;
}
public BigDecimal getPrevValue() {
return prevValue;
}
}
Above code prints:
{"prevValue":3.9E+38}

Jackson Serialization: Unwrap collection elements using

Is there a way to serialize collection and its elements unwrapped?
For example I want to serialize unwrapped all components:
class Model {
#JsonProperty
#JsonUnwrapped
Collection<Object> components;
Model(Collection<Object> components) {
this.components = components;
}
static class Component1 {
#JsonProperty
String stringValue;
Component1(String stringValue) {
this.stringValue= stringValue;
}
}
static class Component2 {
#JsonProperty
int intValue;
Component2(int intValue) {
this.intValue= intValue;
}
}
public static void main(String[] args) throws JsonProcessingException {
Model model = new Model(Arrays.asList(new Component1("something"), new Component2(42)));
String json = new ObjectMapper().writeValueAsString(model);
System.out.println(json);
}
}
Expected:
{"stringValue":"something","intValue":42}
But actual result is:
{"components":[{"stringValue":"something"},{"intValue":42}]}
Custom serializer might help:
class ModelSerializer extends JsonSerializer<Model> {
#Override
public void serialize(Model model, JsonGenerator generator, SerializerProvider serializers) throws IOException {
generator.writeStartObject();
JsonSerializer<Object> componentSerializer = serializers.findValueSerializer(getClass());
JsonSerializer<Object> unwrappingSerializer = componentSerializer.unwrappingSerializer(NameTransformer.NOP);
unwrappingSerializer.serialize(this, generator, serializers);
generator.writeEndObject();
}
}
I can't see a way to do that without custom serialization. I recommend these 2 serializers:
class ValueSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider sers) throws IOException {
for (Field field : value.getClass().getDeclaredFields()) {
try {
field.setAccessible(true);
gen.writeObjectField(field.getName(), field.get(value));
} catch (IllegalAccessException ignored) {
}
}
}
}
class ModelSerializer extends JsonSerializer<Model> {
#Override
public void serialize(Model model, JsonGenerator gen, SerializerProvider sers) throws IOException {
gen.writeStartObject();
for (Object obj : model.getComponents()) {
gen.writeObject(obj);
}
gen.writeEndObject();
}
}
Notice how we don't call writeStartObject() at ValueSerializer so no extra curly braces from here, neither from writeObjectField. On the other hand in ModelSerializer writheStartObject adds curly braces, and then we dump within them each object in components
You'd also need to annotate serializable classes to use these serializers e.g.
#JsonSerialize(using = ValueSerializer.class)
class Component1 {
#JsonSerialize(using = ValueSerializer.class)
class Component2 {
#JsonSerialize(using = ModelSerializer.class)
class Model {
Not elegant, but work code.
Sure about unique naming of key values
#JsonProperty
#JsonSerialize(using = CollectionSerializer.class)
Collection<Object> components;
static class CollectionSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
if (o instanceof Collection) {
Collection c = (Collection) o;
for (Object el : c) {
if (el instanceof Component1) {
jsonGenerator.writeStringField("stringValue", ((Component1) el).stringValue);
}
if (el instanceof Component2) {
jsonGenerator.writeNumberField("intValue", ((Component2) el).intValue);
}
}
}
jsonGenerator.writeEndObject();
}
}

Jackson custom annotation for custom NULL value serialization

According to this answer:
https://stackoverflow.com/a/43342675/5810648
I wrote such serializer:
public class CustomSerializer extends StdSerializer<Double> implements ContextualSerializer {
private final NAifNull annotation;
public CustomSerializer() {
super(Double.class);
this.annotation = null;
}
public CustomSerializer(NAifNull annotation) {
super(Double.class);
this.annotation = annotation;
}
#Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (annotation != null && value == null) {
gen.writeString("N/A");
} else {
gen.writeNumber(value);
}
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
NAifNull annotation = property.getAnnotation(NAifNull.class);
return new CustomSerializer(annotation);
}
}
Witch supposed to write string "N/A" if the annotation is present and field is null. But method serialize is called only for not null fields.
Also, I have tried to call setNullValueSerializer:
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
NAifNull annotation = property.getAnnotation(NAifNull.class);
prov.setNullValueSerializer(new CustomNullSerializer(annotation));
return new CustomSerializer(annotation);
}
With such implementation:
private static class CustomNullSerializer extends JsonSerializer<Object> {
private final NAifNull annotation;
public CustomNullSerializer(NAifNull annotation) {
this.annotation = annotation;
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (annotation != null) {
gen.writeString("N/A");
} else {
gen.writeNull();
}
}
}
But no result.
How to handle null fields in such way?
Update
According to discussion:
https://github.com/FasterXML/jackson-databind/issues/2057
prov.setNullValueSerializer(new CustomNullSerializer(annotation));
Is not supposed to be called from CreateContextual method.
Use a BeanSerializerModifier to customize the null serializer for a particular property:
public class CustomBeanSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter beanProperty : beanProperties) {
if (beanProperty.getAnnotation(NAifNull.class) != null) {
beanProperty.assignNullSerializer(new CustomNullSerializer());
}
}
return beanProperties;
}
}
Where #NAifNull and CustomNullSerializer are define as follows:
public class CustomNullSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
jgen.writeString("N/A");
}
}
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#interface NAifNull {
}
Then use it as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new CustomBeanSerializerModifier());
}
});
If I understood you correctly, you want to write "N/A" to generated JSON, if the value is null.
Jackson docs states that value cannot be null. This is because the type parameter is Class object, which is constructed automatically by JVM.
As per this article, I think you could handle null fields with something like
public class CustomNullSerializer extends StdSerializer<Object> {
public CustomNullSerializer() {
this(null);
}
public CustomNullSerializer(Class<Object> t) {
super(t);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("N/A");
}
}
And then use it with
prov.setNullValueSerializer(new CustomNullSerializer());
Thought I didn't try this myself, but I hope it helps.
UPDATE
Okey, now I had time to try this myself. I got it working with
ObjectMapper mapper...
mapper.getSerializerProvider().setNullValueSerializer(new CustomNullSerializer());

Jackson & JSONAnySetter: NullPointer Exception during Serialization/Deserialization

I have a problem with serialization/deserialization with Jackson 1.9.13 (and Jackson 2.5.0) and fighting this now for a few days without any success.
My goal is to use #JsonAnyGetter & #JsonAnySetter, and I want to calculate dynamically if a object should be written to the output or not. I have a JSON definition which I serialize with the ObjectMapper (and check if the Object should be included or not), and then I convert the object back to a string.
I am using a "HidableSerializer" for this, which works fine during serialization, but not when converting the object back to a string.
Without #JsonAnySetter / -getter or the "HidableSerializer", everythings works fine, but not both together.
Why is this not working? And how can I solve the problem? Better approaches are welcome!
The stack trace looks like this:
Stack Trace
org.codehaus.jackson.map.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: ch.hasselba.Test["[anySetter]"])
null
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218)
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183)
at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
at ch.hasselba.HidableSerializer.serialize(HidableSerializer.java:29)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2575)
at org.codehaus.jackson.map.ObjectMapper.writeValueAsString(ObjectMapper.java:2097)
at ch.hasselba.Demo.main(Demo.java:54)
Caused by: java.lang.NullPointerException
at org.codehaus.jackson.map.ser.std.MapSerializer.serializeFields(MapSerializer.java:243)
at org.codehaus.jackson.map.ser.AnyGetterWriter.getAndSerialize(AnyGetterWriter.java:41)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:154)
... 7 more
The Demo code
package ch.hasselba;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.introspect.BasicBeanDescription;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.ser.BeanSerializerModifier;
public class Demo {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
// register the module
Version version = new Version(1, 0, 0, "SNAPSHOT");
mapper.registerModule(new SimpleModule("HidableModule", version) {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#SuppressWarnings("unchecked")
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BasicBeanDescription desc,
JsonSerializer<?> serializer) {
if (IHidable.class.isAssignableFrom(desc.getBeanClass())) {
return new HidableSerializer<Object>((JsonSerializer<Object>) serializer);
}
return serializer;
}
});
}
});
// the data
String content = "{ \"foo\": \"bar\" }";
// build the Object
Test test = null;
try {
test = mapper.readValue(content, Test.class);
} catch (Exception e) {
e.printStackTrace();
}
// and now convert it back to a String
String data = null;
try {
data = mapper.writeValueAsString(test);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( data );
}
}
Test class
package ch.hasselba;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;
public class Test implements IHidable {
private Map<String, Object> others = new ConcurrentHashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getOthers() {
return this.others;
}
#JsonAnySetter
public void addOther(final String name, final Object value) {
this.others.put(name, value);
}
#Override
public boolean isHidden() {
return false;
}
}
The Hidable Serializer
package ch.hasselba;
import java.io.IOException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class HidableSerializer<T> extends JsonSerializer<T> {
private JsonSerializer<T> defaultSerializer;
public HidableSerializer(JsonSerializer<T> serializer) {
defaultSerializer = serializer;
}
#Override
public void serialize(T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if( value instanceof IHidable ){
IHidable hidableValue = (IHidable) value;
if( hidableValue.isHidden() )
return;
}
defaultSerializer.serialize(value, jgen, provider);
}
}
IHidableInterface
package ch.hasselba;
public interface IHidable {
boolean isHidden();
}
The problem is that the defaultSerializer instance, you are using inside your HidableSerializer, is a ResolvableSerializer (BeanSerializer), but as you wrap it into JsonSerializer (HidableSerializer) in your modifySerializer() method, it's resolve() method is then never invoked and it fails to initialize properly.
If you try adding the following line to your HidableSerializer.serialize() method:
...
((ResolvableSerializer)defaultSerializer).resolve(provider);
defaultSerializer.serialize(value, jgen, provider);
...
it should do the trick.
If this works for you, a more permanent solution would be to make your HidableSerializer implement ResolvableSerializer itself and just delegate resolve() to the defaultSerializer, like this:
#Override
public void resolve(SerializerProvider serializerProvider) throws JsonMappingException {
if(defaultSerializer instanceof ResolvableSerializer) {
((ResolvableSerializer)defaultSerializer).resolve(serializerProvider);
}
}
I went through debugging process and found some code:
if (ser instanceof ResolvableSerializer) {
((ResolvableSerializer) ser).resolve(provider);
}
It actually instantiates key serializer which throws NPE in your case.
Modify your HidableSerializer and it will do the trick:
public class HidableSerializer<T> extends JsonSerializer<T> implements ResolvableSerializer {
private JsonSerializer<T> defaultSerializer;
public HidableSerializer(JsonSerializer<T> serializer) {
defaultSerializer = serializer;
}
#Override
public void serialize(T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if( value instanceof IHidable ){
IHidable hidableValue = (IHidable) value;
if( hidableValue.isHidden() )
return;
}
defaultSerializer.serialize(value, jgen, provider);
}
#Override
public void resolve(SerializerProvider provider) throws JsonMappingException {
((ResolvableSerializer)defaultSerializer).resolve(provider);
}
}

How do I use a custom Serializer with Jackson?

I have two Java classes that I want to serialize to JSON using Jackson:
public class User {
public final int id;
public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
public class Item {
public final int id;
public final String itemNr;
public final User createdBy;
public Item(int id, String itemNr, User createdBy) {
this.id = id;
this.itemNr = itemNr;
this.createdBy = createdBy;
}
}
I want to serialize an Item to this JSON:
{"id":7, "itemNr":"TEST", "createdBy":3}
with User serialized to only include the id. I will also be able to serilize all user objects to JSON like:
{"id":3, "name": "Jonas", "email": "jonas#example.com"}
So I guess that I need to write a custom serializer for Item and tried with this:
public class ItemSerializer extends JsonSerializer<Item> {
#Override
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeNumberField("itemNr", value.itemNr);
jgen.writeNumberField("createdBy", value.user.id);
jgen.writeEndObject();
}
}
I serialize the JSON with this code from Jackson How-to: Custom Serializers:
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule",
new Version(1,0,0,null));
simpleModule.addSerializer(new ItemSerializer());
mapper.registerModule(simpleModule);
StringWriter writer = new StringWriter();
try {
mapper.writeValue(writer, myItem);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
But I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() (use alternative registration method?)
at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62)
at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54)
at com.example.JsonTest.main(JsonTest.java:54)
How can I use a custom Serializer with Jackson?
This is how I would do it with Gson:
public class UserAdapter implements JsonSerializer<User> {
#Override
public JsonElement serialize(User src, java.lang.reflect.Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(src.id);
}
}
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(User.class, new UserAdapter());
Gson gson = builder.create();
String json = gson.toJson(myItem);
System.out.println("JSON: "+json);
But I need to do it with Jackson now, since Gson doesn't have support for interfaces.
You can put #JsonSerialize(using = CustomDateSerializer.class) over any date field of object to be serialized.
public class CustomDateSerializer extends SerializerBase<Date> {
public CustomDateSerializer() {
super(Date.class, true);
}
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)");
String format = formatter.format(value);
jgen.writeString(format);
}
}
As mentioned, #JsonValue is a good way. But if you don't mind a custom serializer, there's no need to write one for Item but rather one for User -- if so, it'd be as simple as:
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeNumber(id);
}
Yet another possibility is to implement JsonSerializable, in which case no registration is needed.
As to error; that is weird -- you probably want to upgrade to a later version. But it is also safer to extend org.codehaus.jackson.map.ser.SerializerBase as it will have standard implementations of non-essential methods (i.e. everything but actual serialization call).
I tried doing this too, and there is a mistake in the example code on the Jackson web page that fails to include the type (.class) in the call to addSerializer() method, which should read like this:
simpleModule.addSerializer(Item.class, new ItemSerializer());
In other words, these are the lines that instantiate the simpleModule and add the serializer (with the prior incorrect line commented out):
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule",
new Version(1,0,0,null));
// simpleModule.addSerializer(new ItemSerializer());
simpleModule.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(simpleModule);
FYI: Here is the reference for the correct example code: http://wiki.fasterxml.com/JacksonFeatureModules
Use #JsonValue:
public class User {
int id;
String name;
#JsonValue
public int getId() {
return id;
}
}
#JsonValue only works on methods so you must add the getId method.
You should be able to skip your custom serializer altogether.
I wrote an example for a custom Timestamp.class serialization/deserialization, but you could use it for what ever you want.
When creating the object mapper do something like this:
public class JsonUtils {
public static ObjectMapper objectMapper = null;
static {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
objectMapper.registerModule(s);
};
}
for example in java ee you could initialize it with this:
import java.time.LocalDateTime;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
objectMapper.registerModule(s);
};
#Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
where the serializer should be something like this:
import java.io.IOException;
import java.sql.Timestamp;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class TimestampSerializerTypeHandler extends JsonSerializer<Timestamp> {
#Override
public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String stringValue = value.toString();
if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
jgen.writeString(stringValue);
} else {
jgen.writeNull();
}
}
#Override
public Class<Timestamp> handledType() {
return Timestamp.class;
}
}
and deserializer something like this:
import java.io.IOException;
import java.sql.Timestamp;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class TimestampDeserializerTypeHandler extends JsonDeserializer<Timestamp> {
#Override
public Timestamp deserialize(JsonParser jp, DeserializationContext ds) throws IOException, JsonProcessingException {
SqlTimestampConverter s = new SqlTimestampConverter();
String value = jp.getValueAsString();
if(value != null && !value.isEmpty() && !value.equals("null"))
return (Timestamp) s.convert(Timestamp.class, value);
return null;
}
#Override
public Class<Timestamp> handledType() {
return Timestamp.class;
}
}
These are behavior patterns I have noticed while trying to understand Jackson serialization.
1) Assume there is an object Classroom and a class Student. I've made everything public and final for ease.
public class Classroom {
public final double double1 = 1234.5678;
public final Double Double1 = 91011.1213;
public final Student student1 = new Student();
}
public class Student {
public final double double2 = 1920.2122;
public final Double Double2 = 2324.2526;
}
2) Assume that these are the serializers we use for serializing the objects into JSON. The writeObjectField uses the object's own serializer if it is registered with the object mapper; if not, then it serializes it as a POJO. The writeNumberField exclusively only accepts primitives as arguments.
public class ClassroomSerializer extends StdSerializer<Classroom> {
public ClassroomSerializer(Class<Classroom> t) {
super(t);
}
#Override
public void serialize(Classroom value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
jgen.writeStartObject();
jgen.writeObjectField("double1-Object", value.double1);
jgen.writeNumberField("double1-Number", value.double1);
jgen.writeObjectField("Double1-Object", value.Double1);
jgen.writeNumberField("Double1-Number", value.Double1);
jgen.writeObjectField("student1", value.student1);
jgen.writeEndObject();
}
}
public class StudentSerializer extends StdSerializer<Student> {
public StudentSerializer(Class<Student> t) {
super(t);
}
#Override
public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
jgen.writeStartObject();
jgen.writeObjectField("double2-Object", value.double2);
jgen.writeNumberField("double2-Number", value.double2);
jgen.writeObjectField("Double2-Object", value.Double2);
jgen.writeNumberField("Double2-Number", value.Double2);
jgen.writeEndObject();
}
}
3) Register only a DoubleSerializer with DecimalFormat output pattern ###,##0.000, in SimpleModule and the output is:
{
"double1" : 1234.5678,
"Double1" : {
"value" : "91,011.121"
},
"student1" : {
"double2" : 1920.2122,
"Double2" : {
"value" : "2,324.253"
}
}
}
You can see that the POJO serialization differentiates between double and Double, using the DoubleSerialzer for Doubles and using a regular String format for doubles.
4) Register DoubleSerializer and ClassroomSerializer, without the StudentSerializer. We expect that the output is such that if we write a double as an object, it behaves like a Double, and if we write a Double as a number, it behaves like a double. The Student instance variable should be written as a POJO and follow the pattern above since it does not register.
{
"double1-Object" : {
"value" : "1,234.568"
},
"double1-Number" : 1234.5678,
"Double1-Object" : {
"value" : "91,011.121"
},
"Double1-Number" : 91011.1213,
"student1" : {
"double2" : 1920.2122,
"Double2" : {
"value" : "2,324.253"
}
}
}
5) Register all serializers. The output is:
{
"double1-Object" : {
"value" : "1,234.568"
},
"double1-Number" : 1234.5678,
"Double1-Object" : {
"value" : "91,011.121"
},
"Double1-Number" : 91011.1213,
"student1" : {
"double2-Object" : {
"value" : "1,920.212"
},
"double2-Number" : 1920.2122,
"Double2-Object" : {
"value" : "2,324.253"
},
"Double2-Number" : 2324.2526
}
}
exactly as expected.
Another important note: If you have multiple serializers for the same class registered with the same Module, then the Module will select the serializer for that class that is most recently added to the list. This should not be used - it's confusing and I am not sure how consistent this is
Moral: if you want to customize serialization of primitives in your object, you must write your own serializer for the object. You cannot rely on the POJO Jackson serialization.
Jackson's JSON Views might be a simpler way of achieving your requirements, especially if you have some flexibility in your JSON format.
If {"id":7, "itemNr":"TEST", "createdBy":{id:3}} is an acceptable representation then this will be very easy to achieve with very little code.
You would just annotate the name field of User as being part of a view, and specify a different view in your serialisation request (the un-annotated fields would be included by default)
For example:
Define the views:
public class Views {
public static class BasicView{}
public static class CompleteUserView{}
}
Annotate the User:
public class User {
public final int id;
#JsonView(Views.CompleteUserView.class)
public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
And serialise requesting a view which doesn't contain the field you want to hide (non-annotated fields are serialised by default):
objectMapper.getSerializationConfig().withView(Views.BasicView.class);
In my case (Spring 3.2.4 and Jackson 2.3.1), XML configuration for custom serializer:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="false">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="serializers">
<array>
<bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
</array>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
was in unexplained way overwritten back to default by something.
This worked for me:
CustomObject.java
#JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {
private Long value;
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
}
CustomObjectSerializer.java
public class CustomObjectSerializer extends JsonSerializer<CustomObject> {
#Override
public void serialize(CustomObject value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("y", value.getValue());
jgen.writeEndObject();
}
#Override
public Class<CustomObject> handledType() {
return CustomObject.class;
}
}
No XML configuration (<mvc:message-converters>(...)</mvc:message-converters>) is needed in my solution.
The problem in your case is the ItemSerializer is missing the method handledType() which needs to be overridden from JsonSerializer
public class ItemSerializer extends JsonSerializer<Item> {
#Override
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeNumberField("itemNr", value.itemNr);
jgen.writeNumberField("createdBy", value.user.id);
jgen.writeEndObject();
}
#Override
public Class<Item> handledType()
{
return Item.class;
}
}
Hence you are getting the explicit error that handledType() is not defined
Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType()
Hope it helps someone. Thanks for reading my answer.
If your only requirement in your custom serializer is to skip serializing the name field of User, mark it as transient. Jackson will not serialize or deserialize transient fields.
[ see also: Why does Java have transient fields? ]
You have to override method handledType and everything will work
#Override
public Class<Item> handledType()
{
return Item.class;
}

Categories