Jackson & JSONAnySetter: исключение NullPointer во время сериализации/десериализации

У меня проблема с сериализацией/десериализацией с Jackson 1.9.13 (и Jackson 2.5.0) и борьба с этим сейчас в течение нескольких дней без каких-либо успехов.

Моя цель - использовать @JsonAnyGetter и @JsonAnySetter, и я хочу рассчитать динамически, если объект должен быть записан на выходе или нет. У меня есть определение JSON, которое я сериализую с ObjectMapper (и проверяю, должен ли Object быть включен или нет), а затем я преобразовываю объект обратно в строку.

Я использую для этого "HidableSerializer", который отлично работает во время сериализации, но не при преобразовании объекта обратно в строку.

Без @JsonAnySetter/-getter или "HidableSerializer" все работает отлично, но не оба вместе.

Почему это не работает? И как я могу решить проблему? Лучшие подходы приветствуются!

Трассировка стека выглядит следующим образом:

Трассировка стека

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

Демо-код

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

    }

}

Класс тестирования

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

}

Скрывающийся сериализатор

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

Ответ 1

Проблема в том, что экземпляр defaultSerializer, который вы используете внутри вашего HidableSerializer, представляет собой ResolvableSerializer (BeanSerializer), но поскольку вы заверните его в JsonSerializer (HidableSerializer) в свой метод modifySerializer(), тогда метод resolve() не будет вызываться и не будет правильно инициализирован.

Если вы попытаетесь добавить следующую строку в свой метод HidableSerializer.serialize():

...
((ResolvableSerializer)defaultSerializer).resolve(provider);
defaultSerializer.serialize(value, jgen, provider);
...

он должен сделать трюк.

Если это сработает для вас, более постоянным решением было бы сделать ваш HidableSerializer реализовать ResolvableSerializer самостоятельно и просто делегировать resol() на defaultSerializer, например:

@Override
public void resolve(SerializerProvider serializerProvider) throws JsonMappingException {
    if(defaultSerializer instanceof ResolvableSerializer) {
         ((ResolvableSerializer)defaultSerializer).resolve(serializerProvider);
    }
}

Ответ 2

Я прошел процесс отладки и нашел код:

if (ser instanceof ResolvableSerializer) {
    ((ResolvableSerializer) ser).resolve(provider);
}

Он фактически запускает ключевой сериализатор, который выдает NPE в вашем случае.

Измените свой HidableSerializer, и он выполнит трюк:

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