(De-)Serialize Bean in a custom way at runtime
jackson, java, json
Solution
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();
}
}
}
Problem
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.