Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(De-)Serialize Bean in a custom way at runtime

Tags:

java

json

jackson

Let's imagine I have the following POJO:

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

And at runtime, I want default serialization / deserialization for all properties except one. Typically, I want to replace a field by its ID in a database when serializing, similarly to this other question.

For example, I want to replace o by a string obtained from an external mapping (for example: object1 <=> "123" and object2 <=> "456"):

  • serialization: read o and replace (so if o is object1, serialize as string "123")
  • deserialization: read "123", query some table to get the original value of o back (i.e. object1), recreate a Pojo object with o = object1.

I understand that Modules would be one way to do that but I'm not sure how to use them while keeping the automatic BeanSerializer/Deserializer for the properties that don't need to be changed.

Can someone give an example (even contrived) or an alternative approach?


Notes:

  • I can't use annotations or Mixins as the changes are unknown at compile time (i.e. any properties might be changed in a way that is not determinable).
  • This other question points to using a CustomSerializerFactory, which seems to do the job. Unfortunately, the official site indicates that it is not the recommended approach any more and that modules should be used instead.

Edit

To be a little clearer, I can do the following with Mixins for example:

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

with the following Mixin:

abstract class PojoMixIn {
    @JsonIgnore Object o;
}

And then add the required string to the JSON content. But I would need to know at compile time that it is the o field that needs to be replaced, which I don't.

like image 962
assylias Avatar asked Feb 22 '13 11:02

assylias


1 Answers

I think @JsonSerialize and @JsonDeserialize is what you need. These annotations give you control on the serialization/deserialization of particular fields. This question shows elegant way to combine them into one annotation.

UPD. For this complex scenario you could take a look at BeanSerializerModifier/BeanDeserializerModifier classes. The idea is to modify general BeanSerializer/BeanDeserializer with your custom logic for particular fields and let basic implementation to do other stuff. Will post an example some time later.

UPD2. As I see, one of the way could be to use changeProperties method and assign your own serializer.

UPD3. Updated with working example of custom serializer. Deserialization could be done in similar way.

UPD4. Updated example with full custom serialization/deserialization. (I have used 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();
        }
    }
}
like image 62
udalmik Avatar answered Oct 26 '22 11:10

udalmik