#JsonFormat not working with custom serializer - java

I have some special business that makes me need a custom serializer,it add a field to the field that modified the DictAnnotation annotation, It works.
But #JsonFormat not working.
My createTime field used to look like this:
"createTime":"2019-12-12"
now:
"createTime":1577835397615
The fields that are not modified by DictAnnotation have used the default JsonSerializer, so I don't know where the problem lies.
My main code is as follows:
bind custom SerializerModifier
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
SimpleModule simpleModule = new SimpleModule().setSerializerModifier(new DictSerializerModifier());
builder.modules(simpleModule);
return builder;
}
custome SerializerModifier
public class DictSerializerModifier extends Jdk8BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
return new DictSerializer(null, (JsonSerializer<Object>) serializer);
}
}
custom Serializer
public class DictSerializer extends JsonSerializer<Object> implements ContextualSerializer {
private static final String MY_BIZ_FIELD_SUFFIX = "_test";
private DictAnnotation.ElementType type;
private JsonSerializer<Object> defSerializer;
public DictSerializer(DictAnnotation.ElementType type, JsonSerializer<Object> jsonSerializer) {
this.defSerializer = jsonSerializer;
this.type = type;
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
// my biz annotation
DictAnnotation.ElementType elementType = Optional.ofNullable(property).map(b -> b.getAnnotation(DictAnnotation.class))
.map(d -> d.type()).orElse(null);
// if null, use defSerializer
return elementType == null ? defSerializer : new DictSerializer(elementType, defSerializer);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
defSerializer.serialize(value, gen, serializers);
if (type != null) {
String fieldName = gen.getOutputContext().getCurrentName();
String codeLabel = RedisUtils.dictCodeToLabel(type, value.toString());
gen.writeStringField(fieldName.concat(MY_BIZ_FIELD_SUFFIX), codeLabel);
}
}
}
test pojo class
public class TestVo {
#DictAnnotation(type = A)
private String aCode;
#DictAnnotation(type = B)
private String bCode;
#DictAnnotation(type = C)
private String cCode;
#JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
private Date createTime;
getter.... settter...
Looking forward to your guidance!

Can you please try if that helps (adding #JsonSerialize like mentioned below)
#JsonSerialize(as = Date.class)
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
private Date createTime;

Related

How do I deserialize JSON documents into pre-processed beans in Jackson?

I'm trying to figure out how to combine the code generator I use in my project and Jackson so that I could combine them both.
The third-party bean code generator does some things that I would like to improve.
For example, the class below
public class Wrapper {
public String string;
public List<String> array;
}
does not have default values set for both string and array.
Under some circumstances (and mostly due to heavy legacy reasons) I'd like Jackson to deserialize the above bean class with the default values set if they are not provided in the input JSON document.
For example, I'd like {"string": "foo"} to be deserialized to a bean as if the source JSON were {"string":"foo","array":[]} so that it would result in a bean with two non-null fields.
The first idea I came up with is creating a bean instance, then run a "set default fields" preprocessor, and then read the JSON into the constructed and initialized bean.
public final class DefaultsModule
extends SimpleModule {
#Override
public void setupModule(final SetupContext setupContext) {
setupContext.addBeanDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(final DeserializationConfig config, final BeanDescription description,
final JsonDeserializer<?> defaultDeserializer) {
return DefaultFieldsJsonDeserializer.create(description.getType(), description);
}
});
}
private static final class DefaultFieldsJsonDeserializer<T>
extends JsonDeserializer<T> {
// the generated classes set is finite, so won't bother with subclassing
private static final Map<Class<?>, Supplier<?>> NEW_INSTANCES = new ImmutableMap.Builder<Class<?>, Supplier<?>>()
.put(Iterable.class, ArrayList::new)
.put(Collection.class, ArrayList::new)
.put(List.class, ArrayList::new)
.put(ArrayList.class, ArrayList::new)
.put(LinkedList.class, LinkedHashMap::new)
.put(Map.class, LinkedHashMap::new)
.put(HashMap.class, HashMap::new)
.put(LinkedHashMap.class, LinkedHashMap::new)
.put(TreeMap.class, TreeMap::new)
.put(Set.class, LinkedHashSet::new)
.put(HashSet.class, HashSet::new)
.put(LinkedHashSet.class, LinkedHashSet::new)
.put(TreeSet.class, TreeSet::new)
.build();
private final BeanDescription description;
private final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain;
private DefaultFieldsJsonDeserializer(final BeanDescription description,
final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain) {
this.description = description;
this.fieldDefaultsChain = fieldDefaultsChain;
}
private static <T> JsonDeserializer<T> create(final JavaType javaType, final BeanDescription description) {
final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain = Stream.of(javaType.getRawClass().getDeclaredFields())
.filter(field -> NEW_INSTANCES.containsKey(field.getType()))
.peek(field -> field.setAccessible(true))
.map(field -> new AbstractMap.SimpleImmutableEntry<Field, Supplier<Object>>(field, () -> NEW_INSTANCES.get(field.getType()).get()))
.collect(Collectors.toList());
return new DefaultFieldsJsonDeserializer<>(description, fieldDefaultsChain);
}
#Override
#Nullable
public T deserialize(final JsonParser parser, final DeserializationContext context)
throws IOException {
try {
// instantiate the bean
#Nullable
#SuppressWarnings("unchecked")
final T bean = (T) description.instantiateBean(false);
if ( bean == null ) {
return null;
}
// do default values pre-processing
for ( final Map.Entry<Field, ? extends Supplier<?>> e : fieldDefaultsChain ) {
final Field field = e.getKey();
final Object defaultValue = e.getValue().get();
field.set(bean, defaultValue);
}
// since the object is constructed and initialized properly, simply update it
final ObjectReader objectReader = ((ObjectMapper) parser.getCodec())
.readerForUpdating(bean);
return objectReader.readValue(parser);
} catch ( final IllegalAccessException ex ) {
return context.reportBadTypeDefinition(description, ex.getMessage());
}
}
}
}
In short, I'd like the following unit test to pass:
public final class DefaultsModuleTest {
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
private static final class Generated {
#JsonProperty
private String string;
#JsonProperty
private List<String> array /*not generated but should be initialized in the pre-processor = new ArrayList<>()*/;
}
#Test
public void test()
throws IOException {
final ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new DefaultsModule());
final Generated expected = new Generated("foo", Collections.emptyList());
Assertions.assertEquals(expected, objectMapper.readValue("{\"string\":\"foo\"}", Generated.class));
Assertions.assertEquals(expected, objectMapper.readValue("{\"string\":\"foo\",\"array\":null}", Generated.class));
Assertions.assertEquals(expected, objectMapper.readValue("{\"string\":\"foo\",\"array\":[]}", Generated.class));
}
}
Unfortunately, the deserializer above runs in the infinite recursion loop.
So I have multiple questions:
how to implement it properly?
maybe I should go with ValueInstantiator somehow?
what is a generic way to get the delegate JSON deserializer? (Gson allows to obtain delegate type adapters in type adapter factories, Jackson offers the deserializer modifier approach but the JsonDeserializer coming in the modifier causes weird exceptions + not sure if it can update existing objects).
My Jackson databind version is 2.9.10.
I seem to have realized the way it had to be done properly. I didn't notice that I can add value instantiators I mentioned above to the module setup context. Having it configured, I simply don't need to create a custom deserializer since I can supply constructed+initialized values myself.
public final class DefaultsModule
extends SimpleModule {
#Override
public void setupModule(final SetupContext setupContext) {
setupContext.addValueInstantiators((config, description, defaultInstantiator) -> DefaultFieldsInstantiator.isSupported(description.getBeanClass())
? DefaultFieldsInstantiator.create(config, description)
: defaultInstantiator
);
}
private static final class DefaultFieldsInstantiator
extends StdValueInstantiator {
private static final Map<Class<?>, Supplier<?>> NEW_INSTANCES = new ImmutableMap.Builder<Class<?>, Supplier<?>>()
.put(Iterable.class, ArrayList::new)
.put(Collection.class, ArrayList::new)
.put(List.class, ArrayList::new)
.put(ArrayList.class, ArrayList::new)
.put(LinkedList.class, LinkedHashMap::new)
.put(Map.class, LinkedHashMap::new)
.put(HashMap.class, HashMap::new)
.put(LinkedHashMap.class, LinkedHashMap::new)
.put(TreeMap.class, TreeMap::new)
.put(Set.class, LinkedHashSet::new)
.put(HashSet.class, HashSet::new)
.put(LinkedHashSet.class, LinkedHashSet::new)
.put(TreeSet.class, TreeSet::new)
.build();
private final BeanDescription description;
private final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain;
private DefaultFieldsInstantiator(final DeserializationConfig config, final BeanDescription description,
final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain) {
super(config, description.getType());
this.description = description;
this.fieldDefaultsChain = fieldDefaultsChain;
}
private static boolean isSupported(final Class<?> clazz) {
return ...............;
}
private static ValueInstantiator create(final DeserializationConfig config, final BeanDescription description) {
final Iterable<? extends Map.Entry<Field, ? extends Supplier<?>>> fieldDefaultsChain = Stream.of(description.getType().getRawClass().getDeclaredFields())
.filter(field -> NEW_INSTANCES.containsKey(field.getType()))
.peek(field -> field.setAccessible(true))
.map(field -> new AbstractMap.SimpleImmutableEntry<Field, Supplier<Object>>(field, () -> NEW_INSTANCES.get(field.getType()).get()))
.collect(Collectors.toList());
return new DefaultFieldsInstantiator(config, description, fieldDefaultsChain);
}
#Override
public boolean canCreateUsingDefault() {
return true;
}
#Override
#Nullable
public Object createUsingDefault(final DeserializationContext context)
throws JsonMappingException {
try {
#Nullable
final Object bean = description.instantiateBean(false);
if ( bean == null ) {
return null;
}
for ( final Map.Entry<Field, ? extends Supplier<?>> e : fieldDefaultsChain ) {
final Field field = e.getKey();
final Object defaultValue = e.getValue().get();
field.set(bean, defaultValue);
}
return bean;
} catch ( final IllegalAccessException ex ) {
return context.reportBadDefinition(description.getType(), "Cannot set field: " + ex.getMessage());
}
}
}
}
And all the following tests pass:
final ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new DefaultsModule());
Assertions.assertNull(objectMapper.readValue("null", Generated.class));
Assertions.assertEquals(new Generated(null, Collections.emptyList()), objectMapper.readValue("{}", Generated.class));
Assertions.assertEquals(new Generated(null, ImmutableList.of("bar")), objectMapper.readValue("{\"array\":[\"bar\"]}", Generated.class));
Assertions.assertEquals(new Generated("foo", Collections.emptyList()), objectMapper.readValue("{\"string\":\"foo\"}", Generated.class));
Assertions.assertEquals(new Generated("foo", null), objectMapper.readValue("{\"string\":\"foo\",\"array\":null}", Generated.class));
Assertions.assertEquals(new Generated("foo", Collections.emptyList()), objectMapper.readValue("{\"string\":\"foo\",\"array\":[]}", Generated.class));

Jackson serialize Object to JSON to base64 (without endless loop)

Is there a simple way to serialize an object using Jackson to base64 encoded JSON? (object -> JSON -> base64)
I tried using a custom StdSerializer, but this (of course) results in a endless loop:
class MySerializer extends StdSerializer<Foo> {
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) {
StringWriter stringWriter = new StringWriter();
JsonGenerator newGen = gen.getCodec().getFactory().createGenerator(stringWriter);
gen.getCodec().getFactory().getCodec().writeValue(newGen, value);
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
A workaround is to copy all fields to another class and use that class for the intermediate representation:
class TmpFoo {
public String field1;
public int field2;
// ...
}
class MySerializer extends StdSerializer<Foo> {
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) {
TmpFoo tmp = new TmpFoo();
tmp.field1 = value.field1;
tmp.field2 = value.field2;
// etc.
StringWriter stringWriter = new StringWriter();
JsonGenerator newGen = gen.getCodec().getFactory().createGenerator(stringWriter);
gen.getCodec().getFactory().getCodec().writeValue(newGen, tmp); // here "tmp" instead of "value"
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
Creating a new ObjectMapper is not desired, because I need all registered modules and serializers of the default ObjectMapper.
I was hoping for some easier way of achieving this.
EDIT: Example
Step 1: Java Object
class Foo {
String field1 = "foo";
int field2 = 42;
}
Step 2: JSON
{"field1":"foo","field2":42}
Step 3: Base64
eyJmaWVsZDEiOiJmb28iLCJmaWVsZDIiOjQyfQ==
According to this site, there is a workaround to avoid this recursion problem:
When we define a custom serializer, Jackson internally overrides the
original BeanSerializer instance [...] our SerializerProvider finds
the customized serializer every time, instead of the default one, and
this causes an infinite loop.
A possible workaround is using BeanSerializerModifier to store the
default serializer for the type Folder before Jackson internally
overrides it.
If I understood the workaround correctly, your Serializer should look like this:
class FooSerializer extends StdSerializer<Foo> {
private final JsonSerializer<Object> defaultSerializer;
public FooSerializer(JsonSerializer<Object> defaultSerializer) {
super(Foo.class);
this.defaultSerializer = defaultSerializer;
}
#Override
public void serialize(Foo value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator tempGen = provider.getGenerator().getCodec().getFactory().createGenerator(stringWriter);
defaultSerializer.serialize(value, tempGen, provider);
tempGen.flush();
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
In addition to the serializer, a modifier is needed:
public class FooBeanSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(
SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass().equals(Foo.class)) {
return new FooSerializer((JsonSerializer<Object>) serializer);
}
return serializer;
}
}
Example module:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FooBeanSerializerModifier());
mapper.registerModule(module);
EDIT:
I've added flush() to flush the JsonGenerator tempGen.
Also, I've created a minimal test enviroment with JUnit, which verifies your Example with Foo: The github repo can be found here.
EDIT: Alternative 2
Another (simple) option is using a wrapper class with generics:
public class Base64Wrapper<T> {
private final T wrapped;
private Base64Wrapper(T wrapped) {
this.wrapped = wrapped;
}
public T getWrapped() {
return this.wrapped;
}
public static <T> Base64Wrapper<T> of(T wrapped) {
return new Base64Wrapper<>(wrapped);
}
}
public class Base64WrapperSerializer extends StdSerializer<Base64Wrapper> {
public Base64WrapperSerializer() {
super(Base64Wrapper.class);
}
#Override
public void serialize(Base64Wrapper value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator tempGen = provider.getGenerator().getCodec().getFactory().createGenerator(stringWriter);
provider.defaultSerializeValue(value.getWrapped(), tempGen);
tempGen.flush();
String json = stringWriter.toString();
String base64 = new String(Base64.getEncoder().encode(json.getBytes()));
gen.writeString(base64);
}
}
An example usecase would be:
final ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(new Base64WrapperSerializer());
mapper.registerModule(module);
final Foo foo = new Foo();
final Base64Wrapper<Foo> base64Wrapper = Base64Wrapper.of(foo);
final String base64Json = mapper.writeValueAsString(base64Wrapper);
This example can be found in this GitHub (branch: wrapper) repo, verifing you BASE64 String from your foo example with JUnit testing.
Instead of creating new object you may convert existing one into map. Like in the example below
import static java.nio.charset.StandardCharsets.UTF_8;
public class FooSerializer extends StdSerializer<Foo> {
public FooSerializer() {
super(Foo.class);
}
#Override
public void serialize(Foo foo, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) {
try {
ObjectMapper mapper = (ObjectMapper) jsonGenerator.getCodec();
var map = toMap(foo); // if you need class info for deserialization than use toMapWithClassInfo
String json = mapper.writeValueAsString(map);
jsonGenerator.writeString(Base64.getEncoder().encodeToString(json.getBytes(UTF_8)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Map<String, Object> toMap(Object o) throws Exception {
Map<String, Object> result = new HashMap<>();
Field[] declaredFields = o.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
result.put(field.getName(), field.get(o));
}
return result;
}
public static Map<String, Object> toMapWithClassInfo(Object obj) throws Exception {
Map<String, Object> result = new HashMap<>();
BeanInfo info = Introspector.getBeanInfo(obj.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
Method reader = pd.getReadMethod();
if (reader != null)
result.put(pd.getName(), reader.invoke(obj));
}
return result;
}
}
I'm providing 2 ways of converting into map: with and without class info. Choose the one, applicable to your problem.
To serialize object jackson search #JsonValue method. You can add encodedJsonString method annotated by #JsonValue in Foo class.
Try with this:
#Getter
#Setter
public class Foo implements Serializable {
private static final long serialVersionUID = 1L;
public String field1;
public int field2;
#JsonValue
public String toEncodedJsonString() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ObjectOutputStream(baos).writeObject(this);
return org.apache.commons.codec.binary.Base64.encodeBase64String(baos.toByteArray());
}catch (Exception ex){
}
return null;
}
}

Jackson Deserialization not calling deserialize on Custom Deserializer

I want to deserialize classes of the form:
public class TestFieldEncryptedMessage implements ITextMessage {
#JsonProperty("text")
#Encrypted(cipherAlias = "testAlias")
private String text;
public TestFieldEncryptedMessage() {
}
#JsonCreator
public TestFieldEncryptedMessage(#JsonProperty("text") String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
Where the text is encrypted and deserialization should unencrypt the value before rebuilding the TestFieldEncryptedMessage instance.
I am following an approach very similar to: https://github.com/codesqueak/jackson-json-crypto
That is, I am building a module extending SimpleModule:
public class CryptoModule extends SimpleModule {
public final static String GROUP_ID = "au.com.auspost.messaging";
public final static String ARTIFACT_ID = "jackson-json-crypto";
private EncryptedSerializerModifier serializerModifier;
private EncryptedDeserializerModifier deserializerModifier;
public CryptoModule() {
}
public CryptoModule addEncryptionService(final EncryptionService encryptionService) {
serializerModifier = new EncryptedSerializerModifier(encryptionService);
deserializerModifier = new EncryptedDeserializerModifier(encryptionService);
return this;
}
#Override
public String getModuleName() {
return ARTIFACT_ID;
}
#Override
public Version version() {
return new Version(major, minor, patch, null, GROUP_ID, ARTIFACT_ID);
}
#Override
public void setupModule(final SetupContext context) {
if ((null == serializerModifier) || (null == deserializerModifier))
throw new EncryptionException("Crypto module not initialised with an encryption service");
context.addBeanSerializerModifier(serializerModifier);
context.addBeanDeserializerModifier(deserializerModifier);
}
}
As you can see, two modifiers are set up: the EncryptedSerializerModifier works perfectly and is called by the ObjectMapper, but the deserializer behind the EncryptedDeserializerModifier is ignored.
As is seen in many other examples on SO such as here: How can I include raw JSON in an object using Jackson?, I set up the EncryptedDeserializerModifier with:
public class EncryptedDeserializerModifier extends BeanDeserializerModifier {
private final EncryptionService encryptionService;
private Map<String, SettableBeanProperty> properties = new HashMap<>();
public EncryptedDeserializerModifier(final EncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
#Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config, final BeanDescription beanDescription, final BeanDeserializerBuilder builder) {
Encrypted annotation = beanDescription.getType().getRawClass().getAnnotation(Encrypted.class);
Iterator it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty p = (SettableBeanProperty) it.next();
if (null != p.getAnnotation(Encrypted.class)) {
JsonDeserializer<Object> current = p.getValueDeserializer();
properties.put(p.getName(), p);
builder.addOrReplaceProperty(p.withValueDeserializer(new EncryptedJsonDeserializer(encryptionService, current, p)), true);
}
}
return builder;
}
}
Finally, the EncryptedJsonDeserializer itself overrides the following:
#Override
public Object deserialize(final JsonParser parser, final DeserializationContext context) throws JsonMappingException {
JsonDeserializer<?> deserializer = baseDeserializer;
if (deserializer instanceof ContextualDeserializer) {
deserializer = ((ContextualDeserializer) deserializer).createContextual(context, property);
}
return service.decrypt(parser, deserializer, context, property != null ? property.getType() : type);
}
#Override
public JsonDeserializer<?> createContextual(final DeserializationContext context, final BeanProperty property) throws JsonMappingException {
JsonDeserializer<?> wrapped = context.findRootValueDeserializer(property.getType());
return new EncryptedJsonDeserializer(service, wrapped, property);
}
The createContextual() method is called, but the deserialize method is not called. The property throughout the execution is always the "text" property, so I seem to have the right context.
anyone know why the ObjectMapper doesn't find the right Deserializer?
EDIT added implements ITextMessage to decrypted class, which I thought was an unimportant detail, but turned out to be the cause of the issue.
I found the issue! If you look closely at the TestFieldEncryptedMessage class, whose text field is encrypted, you can see that it implements an interface. The interface is used so that the messages give some extra tooling for asserts in tests, however for deserialization, there is an unintended consequence. When the ObjectMapper is working its way through the json string, it tries, I think, to match a deserializer to a field inside ITextMessage, not to a field inside TestFieldEncryptedMessage, which is why the custom deserializer was not called (there is no text field in ITextMessage).
Once I stopped implementing ITextMessage, the custom deserializer was called.

Jackson: XML-Binding - Handling Null vs. Empty String

The new Jackson-API provides us with convenient XML-Binding (just like JAXB for example), but i cant find any way to make Jackson serialize the typical "xsi:nil"-Attribute that is defacto standard to represent NULL-Values in XML?
Please correct me if i see this wrong ;-)
In JAXB this can be done easily by annotating a java-variable with:
#XMLElement(nillable=true)
see also: http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html
Can Jackson do this ?
for Jackson-XML see: https://github.com/FasterXML/jackson-dataformat-xml
This does not answer the question but provides a workaround (very hacky)!
I managed to write some custom serializers/deserializers for jackson (until jackson officially supports xsi:nil), that allow the following:
serialize Values from a POJO as xsi:nil elements to a XML-String if they are NULL in the POJO
deserialize a list of hardcoded Types (String,Integer,Float...) as NULL to a POJO if they are defined as xsi:nil elements in the given XML-String
With this code one can provide interoperability to other xml-binding libraries (JAXB..) that can only work with xsi:nil for null-values.
Class Item:
public class Item {
public String x;
public Integer y;
public Integer z;
}
Class Main:
public class Main {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
NumberDeserializers numberDeserializers = new NumberDeserializers();
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// create custom-serialization
XmlSerializerProvider provider = new XmlSerializerProvider(new XmlRootNameLookup());
provider.setNullValueSerializer(new MyNullSerializer());
xmlMapper.setSerializerProvider(provider);
// create custom deserialization
SimpleModule myModule = new SimpleModule("Module", new Version(1, 9, 10, "FINAL"));
myModule.addDeserializer(String.class, new NullableDeserializer(new StringDeserializer()));
myModule.addDeserializer(Number.class, new NullableDeserializer(numberDeserializers.find(Integer.class, Integer.class.getName())));
myModule.addDeserializer(Float.class, new NullableDeserializer(numberDeserializers.find(Float.class, Float.class.getName())));
xmlMapper.registerModule(myModule);
// deserialize
Item value = xmlMapper.readValue(
"<item xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ><a></a><x xsi:nil=\"true\"></x><y/><z>13</z></item>",
Item.class);
// serialize
String xml = xmlMapper.writeValueAsString(value);
System.out.println(xml);
}
}
Class MyNullSerializer:
public class MyNullSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
ToXmlGenerator xGen = (ToXmlGenerator) jgen;
xGen.writeStartObject();
try {
xGen.getStaxWriter().writeAttribute("xsi:nil", "true");
} catch (Exception e){
e.printStackTrace();
}
xGen.writeEndObject();
}
}
Class MyNullDeserializer:
public class MyNullDeserializer extends JsonDeserializer {
private JsonDeserializer delegate;
public MyNullDeserializer(JsonDeserializer delegate){
this.delegate = delegate;
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
FromXmlParser fxp = (FromXmlParser) jp;
boolean isNil = false;
XMLStreamReader reader = fxp.getStaxReader();
if (reader.isStartElement()){
if (reader.getAttributeCount() > 0){
String atVal = reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "nil");
if (atVal != null){
if (Boolean.parseBoolean(atVal) == true){
isNil = true;
}
}
}
}
Object value = null;
if (isNil == false){
value = delegate.deserialize(jp, ctxt);
} else {
jp.getValueAsString(); // move forward
}
return value;
}
}
I expanded on the work of rnd since it enables the feature for all fields and not just some of them.
This is a module you will add to your bindings as follows:
XmlMapper mapper = new XmlMapper();
XmlSerializerProvider provider = new XmlSerializerProvider(new XmlRootNameLookup());
provider.setNullValueSerializer(new NullSerializer());
mapper.setSerializerProvider(provider);
mapper.registerModule(new NullPointerModule());
NullPointerModule implements its own customized serializer to pass a property needed for introspection of the current field.
NullPointerModule.java:
public class NullPointerModule extends SimpleModule implements java.io.Serializable {
private static final long serialVersionUID = 1L;
#Override
public void setupModule(SetupContext context) {
// Need to modify BeanDeserializer, BeanSerializer that are used
context.addBeanSerializerModifier(new XmlBeanSerializerModifier() {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0, len = beanProperties.size(); i < len; ++i) {
BeanPropertyWriter bpw = beanProperties.get(i);
if (bpw.getClass().equals(BeanPropertyWriter.class)) {
beanProperties.set(i, new NullCheckedBeanPropertyWriter(bpw));
}
}
return beanProperties;
}
});
super.setupModule(context);
}
}
Next is the actual NullSerializer, this accepts the property writer and determines if the field does need the nil field or not.
NullSerializer.java:
public class NullSerializer extends JsonSerializer<Object> {
#SuppressWarnings("unused")
public void serializeWithProperty(BeanPropertyWriter propertyWriter, Object value, JsonGenerator jgen, SerializerProvider provider) {
ToXmlGenerator xGen = (ToXmlGenerator) jgen;
XmlElement annotation = null;
if (propertyWriter != null) {
AnnotatedMember member = propertyWriter.getMember();
annotation = member.getAnnotation(XmlElement.class);
}
try {
if (annotation != null) {
if (annotation.nillable()) {
xGen.writeStartObject();
XMLStreamWriter staxWriter = xGen.getStaxWriter();
staxWriter.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
staxWriter.writeAttribute("xsi:nil", "true");
xGen.writeEndObject();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
serializeWithProperty(null, value, jgen, provider);
}
}
Lastly is the override for the propertyWriters. This is a bit of a hack since this can fail if the property writer itself was replaced by another class in another module.
NullCheckedBeanPropertyWriter.java:
public class NullCheckedBeanPropertyWriter extends BeanPropertyWriter {
public NullCheckedBeanPropertyWriter(BeanPropertyWriter base) {
super(base);
}
#Override
public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
final Object value = (_accessorMethod == null) ? _field.get(bean)
: _accessorMethod.invoke(bean);
// Null handling is bit different, check that first
if (value == null) {
if (_nullSerializer != null) {
gen.writeFieldName(_name);
if (_nullSerializer instanceof NullSerializer) {
NullSerializer nullSerializer = (NullSerializer) _nullSerializer;
nullSerializer.serializeWithProperty(this, bean, gen, prov);
return;
}
_nullSerializer.serialize(null, gen, prov);
}
return;
}
super.serializeAsField(bean, gen, prov);
}
}
The fields can then be added with #XmlElement(nillable=true) to make them work to your needs.

Jackson: How to add custom property to the JSON without modifying the POJO

I am developing a REST interface for my app using Jackson to serialize my POJO domain objects to JSON representation. I want to customize the serialization for some types to add additional properties to the JSON representation that do not exist in POJOs (e.g. add some metadata, reference data, etc). I know how to write my own JsonSerializer, but in that case I would need to explicitly call JsonGenerator.writeXXX(..) methods for each property of my object while all I need is just to add an additional property. In other words I would like to be able to write something like:
#Override
public void serialize(TaxonomyNode value, JsonGenerator jgen, SerializerProvider provider) {
jgen.writeStartObject();
jgen.writeAllFields(value); // <-- The method I'd like to have
jgen.writeObjectField("my_extra_field", "some data");
jgen.writeEndObject();
}
or (even better) to somehow intercept the serialization before the jgen.writeEndObject() call, e.g.:
#Override void beforeEndObject(....) {
jgen.writeObjectField("my_extra_field", "some data");
}
I thought I could extend BeanSerializer and override its serialize(..) method but it's declared final and also I couldn't find an easy way to create a new instance of BeanSerializer without providing it with all the type metadata details practically duplicating a good portion of Jackson. So I've given up on doing that.
My question is - how to customize Jackson's serialization to add additional stuff to the JSON output for particular POJOs without introducing too much of the boilerplate code and reusing as much as possible of the default Jackson behaviour.
Jackson 2.5 introduced the #JsonAppend annotation, which can be used to add "virtual" properties during serialization. It can be used with the mixin functionality to avoid modifying the original POJO.
The following example adds an ApprovalState property during serialization:
#JsonAppend(
attrs = {
#JsonAppend.Attr(value = "ApprovalState")
}
)
public static class ApprovalMixin {}
Register the mixin with the ObjectMapper:
mapper.addMixIn(POJO.class, ApprovalMixin.class);
Use an ObjectWriter to set the attribute during serialization:
ObjectWriter writer = mapper.writerFor(POJO.class)
.withAttribute("ApprovalState", "Pending");
Using the writer for serialization will add the ApprovalState field to the ouput.
Since (I think) Jackson 1.7 you can do this with a BeanSerializerModifier and extending BeanSerializerBase. I've tested the example below with Jackson 2.0.4.
import java.io.IOException;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
public class JacksonSerializeWithExtraField {
#Test
public void testAddExtraField() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new BeanSerializerModifier() {
public JsonSerializer<?> modifySerializer(
SerializationConfig config,
BeanDescription beanDesc,
JsonSerializer<?> serializer) {
if (serializer instanceof BeanSerializerBase) {
return new ExtraFieldSerializer(
(BeanSerializerBase) serializer);
}
return serializer;
}
});
}
});
mapper.writeValue(System.out, new MyClass());
//prints {"classField":"classFieldValue","extraField":"extraFieldValue"}
}
class MyClass {
private String classField = "classFieldValue";
public String getClassField() {
return classField;
}
public void setClassField(String classField) {
this.classField = classField;
}
}
class ExtraFieldSerializer extends BeanSerializerBase {
ExtraFieldSerializer(BeanSerializerBase source) {
super(source);
}
ExtraFieldSerializer(ExtraFieldSerializer source,
ObjectIdWriter objectIdWriter) {
super(source, objectIdWriter);
}
ExtraFieldSerializer(ExtraFieldSerializer source,
String[] toIgnore) {
super(source, toIgnore);
}
protected BeanSerializerBase withObjectIdWriter(
ObjectIdWriter objectIdWriter) {
return new ExtraFieldSerializer(this, objectIdWriter);
}
protected BeanSerializerBase withIgnorals(String[] toIgnore) {
return new ExtraFieldSerializer(this, toIgnore);
}
public void serialize(Object bean, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonGenerationException {
jgen.writeStartObject();
serializeFields(bean, jgen, provider);
jgen.writeStringField("extraField", "extraFieldValue");
jgen.writeEndObject();
}
}
}
You can do this (previous version did not work with Jackson after 2.6, but this works with Jackson 2.7.3):
public static class CustomModule extends SimpleModule {
public CustomModule() {
addSerializer(CustomClass.class, new CustomClassSerializer());
}
private static class CustomClassSerializer extends JsonSerializer {
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
//Validate.isInstanceOf(CustomClass.class, value);
jgen.writeStartObject();
JavaType javaType = provider.constructType(CustomClass.class);
BeanDescription beanDesc = provider.getConfig().introspect(javaType);
JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
javaType,
beanDesc);
// this is basically your 'writeAllFields()'-method:
serializer.unwrappingSerializer(null).serialize(value, jgen, provider);
jgen.writeObjectField("my_extra_field", "some data");
jgen.writeEndObject();
}
}
}
Update:
I tried it out with Jackson 2.9.0 and 2.9.6 and it worked as expected with both. Perhaps try this out: http://jdoodle.com/a/z99 (run it locally - jdoodle apparently can't handle Jackson).
Though this question is already answered, I found another way that requires no special Jackson hooks.
static class JsonWrapper<T> {
#JsonUnwrapped
private T inner;
private String extraField;
public JsonWrapper(T inner, String field) {
this.inner = inner;
this.extraField = field;
}
public T getInner() {
return inner;
}
public String getExtraField() {
return extraField;
}
}
static class BaseClass {
private String baseField;
public BaseClass(String baseField) {
this.baseField = baseField;
}
public String getBaseField() {
return baseField;
}
}
public static void main(String[] args) throws JsonProcessingException {
Object input = new JsonWrapper<>(new BaseClass("inner"), "outer");
System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(input));
}
Outputs:
{
"baseField" : "inner",
"extraField" : "outer"
}
For writing collections, you can simply use a view:
public static void main(String[] args) throws JsonProcessingException {
List<BaseClass> inputs = Arrays.asList(new BaseClass("1"), new BaseClass("2"));
//Google Guava Library <3
List<JsonWrapper<BaseClass>> modInputs = Lists.transform(inputs, base -> new JsonWrapper<>(base, "hello"));
System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(modInputs));
}
Output:
[ {
"baseField" : "1",
"extraField" : "hello"
}, {
"baseField" : "2",
"extraField" : "hello"
} ]
Another and perhaps the most simple solution:
Make serialisation a 2-step process. First create a Map<String,Object> like:
Map<String,Object> map = req.mapper().convertValue( result, new TypeReference<Map<String,Object>>() {} );
then add the properties you want like:
map.put( "custom", "value" );
then serialise this to json:
String json = req.mapper().writeValueAsString( map );
For my use case, I could use a much simpler way. In a the base class I have for all my "Jackson Pojos" I add:
protected Map<String,Object> dynamicProperties = new HashMap<String,Object>();
...
public Object get(String name) {
return dynamicProperties.get(name);
}
// "any getter" needed for serialization
#JsonAnyGetter
public Map<String,Object> any() {
return dynamicProperties;
}
#JsonAnySetter
public void set(String name, Object value) {
dynamicProperties.put(name, value);
}
I can now deserialize to Pojo, work with fields and reserialize witjout losing any properties. I can also add/change non pojo properties:
// Pojo fields
person.setFirstName("Annna");
// Dynamic field
person.set("ex", "test");
(Got it from Cowtowncoder)
We can use reflection to get all the fields of the object you want to parse.
#JsonSerialize(using=CustomSerializer.class)
class Test{
int id;
String name;
String hash;
}
In custom serializer, we have our serialize method like this :
#Override
public void serialize(Test value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
Field[] fields = value.getClass().getDeclaredFields();
for (Field field : fields) {
try {
jgen.writeObjectField(field.getName(), field.get(value));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
jgen.writeObjectField("extra_field", "whatever_value");
jgen.writeEndObject();
}
Inspired from what wajda said and written in this gist:
Here is how to add a listener for bean serialization in jackson 1.9.12. In this example, the listerner is considered as a Chain Of Command which interface is :
public interface BeanSerializerListener {
void postSerialization(Object value, JsonGenerator jgen) throws IOException;
}
MyBeanSerializer.java:
public class MyBeanSerializer extends BeanSerializerBase {
private final BeanSerializerListener serializerListener;
protected MyBeanSerializer(final BeanSerializerBase src, final BeanSerializerListener serializerListener) {
super(src);
this.serializerListener = serializerListener;
}
#Override
public void serialize(final Object bean, final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonGenerationException {
jgen.writeStartObject();
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, jgen, provider);
} else {
serializeFields(bean, jgen, provider);
}
serializerListener.postSerialization(bean, jgen);
jgen.writeEndObject();
}
}
MyBeanSerializerBuilder.java:
public class MyBeanSerializerBuilder extends BeanSerializerBuilder {
private final BeanSerializerListener serializerListener;
public MyBeanSerializerBuilder(final BasicBeanDescription beanDesc, final BeanSerializerListener serializerListener) {
super(beanDesc);
this.serializerListener = serializerListener;
}
#Override
public JsonSerializer<?> build() {
BeanSerializerBase src = (BeanSerializerBase) super.build();
return new MyBeanSerializer(src, serializerListener);
}
}
MyBeanSerializerFactory.java:
public class MyBeanSerializerFactory extends BeanSerializerFactory {
private final BeanSerializerListener serializerListener;
public MyBeanSerializerFactory(final BeanSerializerListener serializerListener) {
super(null);
this.serializerListener = serializerListener;
}
#Override
protected BeanSerializerBuilder constructBeanSerializerBuilder(final BasicBeanDescription beanDesc) {
return new MyBeanSerializerBuilder(beanDesc, serializerListener);
}
}
The last class below shows how to provide it using Resteasy 3.0.7:
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
private final MapperConfigurator mapperCfg;
public ObjectMapperProvider() {
mapperCfg = new MapperConfigurator(null, null);
mapperCfg.setAnnotationsToUse(new Annotations[]{Annotations.JACKSON, Annotations.JAXB});
mapperCfg.getConfiguredMapper().setSerializerFactory(serializerFactory);
}
#Override
public ObjectMapper getContext(final Class<?> type) {
return mapperCfg.getConfiguredMapper();
}
}
We can extend BeanSerializer, but with little trick.
First, define a java class to wrapper your POJO.
#JsonSerialize(using = MixinResultSerializer.class)
public class MixinResult {
private final Object origin;
private final Map<String, String> mixed = Maps.newHashMap();
#JsonCreator
public MixinResult(#JsonProperty("origin") Object origin) {
this.origin = origin;
}
public void add(String key, String value) {
this.mixed.put(key, value);
}
public Map<String, String> getMixed() {
return mixed;
}
public Object getOrigin() {
return origin;
}
}
Then,implement your custom serializer.
public final class MixinResultSerializer extends BeanSerializer {
public MixinResultSerializer() {
super(SimpleType.construct(MixinResult.class), null, new BeanPropertyWriter[0], new BeanPropertyWriter[0]);
}
public MixinResultSerializer(BeanSerializerBase base) {
super(base);
}
#Override
protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (bean instanceof MixinResult) {
MixinResult mixin = (MixinResult) bean;
Object origin = mixin.getOrigin();
BeanSerializer serializer = (BeanSerializer) provider.findValueSerializer(SimpleType.construct(origin.getClass()));
new MixinResultSerializer(serializer).serializeFields(origin, gen, provider);
mixin.getMixed().entrySet()
.stream()
.filter(entry -> entry.getValue() != null)
.forEach((entry -> {
try {
gen.writeFieldName(entry.getKey());
gen.writeRawValue(entry.getValue());
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
} else {
super.serializeFields(bean, gen, provider);
}
}
}
This way, we can handle the case that origin object using jackson annotations to custom serialize behavior.
I needed this ability as well; in my case, to support field expansion on REST services. I ended up developing a tiny framework to solve this problem, and it's open sourced on github. It's also available in the maven central repository.
It takes care of all the work. Simply wrap the POJO in a MorphedResult, and then add or remove properties at will. When serialized, the MorphedResult wrapper disappears and any 'changes' appear in the serialized JSON object.
MorphedResult<?> result = new MorphedResult<>(pojo);
result.addExpansionData("my_extra_field", "some data");
See the github page for more details and examples. Be sure to register the libraries 'filter' with Jackson's object mapper like so:
ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new FilteredResultProvider());
This google groups thread points to the BeanSerializerModifier.changeProperties method:
https://groups.google.com/g/jackson-user/c/uYIxbRZhsIM/m/1QpLh7G72C0J
It looks like this method makes the least interference with the object serialization, which is very convenient if you have other serialization customizations.
You can add more objects to the given beanProperties list.
Suppose, we have this bean to be serialized:
public class MyClass {
private final String name;
private final String description;
public MyClass(String name, String description) {
this.name = name;
this.description = description;
}
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
return name;
}
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getDescription() {
return description;
}
}
Then you can add a SerializerModifier to your ObjectMapper instance.
The most interesting parts are the MyBeanSerializerModifier.changeProperties and the CustomPropertyWriter.value methods.
private void addSerializationCustomization(ObjectMapper objectMapper,
SomeAdditionalDataFactory dataFactory) {
SimpleModule module = new SimpleModule();
BeanSerializerModifier modifier = new MyBeanSerializerModifier(dataFactory);
module.setSerializerModifier(modifier);
objectMapper.registerModule(module);
}
private static class MyBeanSerializerModifier extends BeanSerializerModifier {
private final SomeAdditionalDataFactory dataFactory;
public MyBeanSerializerModifier(SomeAdditionalDataFactory dataFactory) {
this.dataFactory = dataFactory;
}
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
if (MyClass.class.isAssignableFrom(beanDesc.getBeanClass())) {
Map<String, Function<MyClass, String>> additionalFields = Map.of(
"someData1",
myObj -> dataFactory.getSomeData1(myObj),
"someData2",
myObj -> dataFactory.getSomeData2(myObj),
"someData3",
myObj -> dataFactory.getSomeData3(myObj)
);
JavaType javaType = SimpleType.constructUnsafe(String.class);
for (Map.Entry<String, Function<MyClass, String>> entry : additionalFields.entrySet()) {
VirtualAnnotatedMember member = new VirtualAnnotatedMember(
null, beanDesc.getBeanClass(), entry.getKey(), javaType);
BeanPropertyDefinition definition = SimpleBeanPropertyDefinition
.construct(config, member, new PropertyName(entry.getKey()));
BeanPropertyWriter writer = new CustomPropertyWriter<>(
definition, javaType, entry.getValue());
beanProperties.add(writer);
}
}
return super.changeProperties(config, beanDesc, beanProperties);
}
}
private static class CustomPropertyWriter<T> extends VirtualBeanPropertyWriter {
private final Function<T, String> getter;
public CustomPropertyWriter(BeanPropertyDefinition propDef,
JavaType declaredType,
Function<T, String> getter) {
super(propDef, null, declaredType);
this.getter = getter;
}
#Override
#SuppressWarnings("unchecked")
protected Object value(Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
return getter.apply((T) bean);
}
#Override
public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config,
AnnotatedClass declaringClass,
BeanPropertyDefinition propDef,
JavaType type) {
throw new IllegalStateException("Should not be called on this type");
}
}
After looking more on the Jackson source code I concluded that it's simply impossible to achieve without writing my own BeanSerializer, BeanSerializerBuilder and BeanSerializerFactory and provide some extension points like:
/*
/**********************************************************
/* Extension points
/**********************************************************
*/
protected void beforeEndObject(T bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JSONException {
// May be overridden
}
protected void afterStartObject(T bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JSONException {
// May be overridden
}
Unfortunately I had to copy and paste entire Jackson's BeanSerializer source code to MyCustomBeanSerializer because the former is not developed for extensions declaring all the fields and some important methods (like serialize(...)) as final

Categories

Resources