(De-) Сериализовать Bean по-своему во время выполнения

Предположим, что у меня есть следующее POJO:

class Pojo {
    String s;
    Object o;
    Map<String, String> m;
}

И во время выполнения, я хочу сериализации/десериализации по умолчанию для всех свойств, кроме одного. Как правило, я хочу заменить поле его идентификатором в базе данных при сериализации, аналогично этому другому вопросу.

Например, я хочу заменить o на строку, полученную из внешнего сопоставления (например: object1 <= > "123" и object2 <= > "456" ):

  • Сериализация: прочитайте o и замените (так что если o есть object1, сериализуйтесь как строка "123" )
  • deserialization: прочитайте "123" , запросите некоторую таблицу, чтобы получить исходное значение o назад (т.е. object1), воссоздайте объект Pojo с помощью o = object1.

Я понимаю, что Модули будут одним из способов сделать это, но я не уверен, как их использовать, сохраняя автоматический BeanSerializer/Deserializer для свойств, которые не нужно изменять.

Может ли кто-нибудь привести пример (даже надуманный) или альтернативный подход?


Примечания:

  • Я не могу использовать аннотации или Mixins, поскольку изменения неизвестны во время компиляции (т.е. любые свойства могут быть изменены способом, который не может быть определен).
  • Этот другой вопрос указывает на использование CustomSerializerFactory, который, похоже, выполняет эту работу. К сожалению, на официальном сайте указано, что это не рекомендуемый подход и что модули должны использоваться вместо этого.

Edit

Чтобы быть немного яснее, я могу сделать следующее с помощью Mixins, например:

ObjectMapper mapper = new ObjectMapper(MongoBsonFactory.createFactory());
mapper.addMixInAnnotations(Pojo.class, PojoMixIn.class);

ObjectReader reader = mapper.reader(Pojo.class);
DBEncoder dbEncoder = DefaultDBEncoder.FACTORY.create();
OutputBuffer buffer = new BasicOutputBuffer();
dbEncoder.writeObject(buffer, o);

со следующим Mixin:

abstract class PojoMixIn {
    @JsonIgnore Object o;
}

Затем добавьте нужную строку в содержимое JSON. Но во время компиляции мне нужно знать, что это поле o, которое нужно заменить, чего я не делаю.

Ответ 1

Я думаю, что @JsonSerialize и @JsonDeserialize - это то, что вам нужно. Эти аннотации позволяют контролировать сериализацию/десериализацию отдельных полей. Этот вопрос показывает элегантный способ объединить их в одну аннотацию.

UPD.. Для этого сложного сценария вы можете взглянуть на классы BeanSerializerModifier/BeanDeserializerModifier. Идея состоит в том, чтобы изменить общий BeanSerializer/BeanDeserializer с помощью вашей специальной логики для определенных полей и позволить базовой реализации делать другие вещи. Выведет пример спустя некоторое время.

UPD2. Как я вижу, одним из способов может быть использование метода changeProperties и назначение собственного сериализатора.

UPD3. Обновлен рабочим примером пользовательского сериализатора. Дезициализация может быть выполнена аналогичным образом.

UPD4. Обновленный пример с полной пользовательской сериализацией/десериализации. (Я использовал jakson-mapper-asl-1.9.8)

  public class TestBeanSerializationModifiers {

    static final String PropertyName = "customProperty";
    static final String CustomValue = "customValue";
    static final String BaseValue = "baseValue";

    // Custom serialization

    static class CustomSerializer extends JsonSerializer<Object> {
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            String customValue = CustomValue; // someService.getCustomValue(value);
            jgen.writeString(customValue);
        }
    }

    static class MyBeanSerializerModifier extends BeanSerializerModifier {
        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BasicBeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
            for (int i = 0; i < beanProperties.size(); i++) {
                BeanPropertyWriter beanPropertyWriter = beanProperties.get(i);
                if (PropertyName.equals(beanPropertyWriter.getName())) {
                    beanProperties.set(i, beanPropertyWriter.withSerializer(new CustomSerializer()));
                }
            }
            return beanProperties;
        }
    }

    // Custom deserialization

    static class CustomDeserializer extends JsonDeserializer<Object> {
        @Override
        public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            // serialized value, 'customValue'
            String serializedValue = jp.getText();
            String baseValue = BaseValue; // someService.restoreOldValue(serializedValue);
            return baseValue;
        }
    }

    static class MyBeanDeserializerModifier extends BeanDeserializerModifier {
        @Override
        public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BasicBeanDescription beanDesc, BeanDeserializerBuilder builder) {
            Iterator<SettableBeanProperty> beanPropertyIterator = builder.getProperties();
            while (beanPropertyIterator.hasNext()) {
                SettableBeanProperty settableBeanProperty = beanPropertyIterator.next();
                if (PropertyName.equals(settableBeanProperty.getName())) {
                    SettableBeanProperty newSettableBeanProperty = settableBeanProperty.withValueDeserializer(new CustomDeserializer());
                    builder.addOrReplaceProperty(newSettableBeanProperty, true);
                    break;
                }
            }
            return builder;
        }
    }

    static class Model {

        private String customProperty = BaseValue;
        private String[] someArray = new String[]{"one", "two"};

        public String getCustomProperty() {
            return customProperty;
        }

        public void setCustomProperty(String customProperty) {
            this.customProperty = customProperty;
        }

        public String[] getSomeArray() {
            return someArray;
        }

        public void setSomeArray(String[] someArray) {
            this.someArray = someArray;
        }
    }

    public static void main(String[] args) {
        SerializerFactory serializerFactory = BeanSerializerFactory
                .instance
                .withSerializerModifier(new MyBeanSerializerModifier());

        DeserializerFactory deserializerFactory = BeanDeserializerFactory
                .instance
                .withDeserializerModifier(new MyBeanDeserializerModifier());

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializerFactory(serializerFactory);
        objectMapper.setDeserializerProvider(new StdDeserializerProvider(deserializerFactory));

        try {
            final String fileName = "test-serialization.json";
            // Store, "customValue" -> json
            objectMapper.writeValue(new File(fileName), new Model());
            // Restore, "baseValue" -> model
            Model model = objectMapper.readValue(new File(fileName), Model.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}