Deserializing fails for a class implementing Collection with Jackson - java

I have the following JSON:
{
"item": [
{ "foo": 1 },
{ "foo": 2 }
]
}
This is basically an object that contains a collection of items.
So I made a class to deserialize that:
public class ItemList {
#JsonProperty("item")
List<Item> items;
// Getters, setters & co.
// ...
}
Everything is working nicely up to this point.
Now, To make my life easier somewhere else, I decided that it would be nice to be able to iterate on the ItemList object and let it implement the Collection interface.
So basically my class became:
public class ItemList implements Collection<Item>, Iterable<Item> {
#JsonProperty("item")
List<Item> items;
// Getters, setters & co.
// Generated all method delegates to items. For instance:
public Item get(int position) {
return items.get(position);
}
}
The implementation works properly and nicely. However, the deserialization now fails.
Looks like Jackson is getting confused:
com.fasterxml.jackson.databind.JsonMappingException: Can not
deserialize instance of com.example.ItemList out of START_OBJECT token
I have tried to add #JsonDeserialize(as=ItemList.class) but it did not do the trick.
What's the way to go?

Obviously it does not work because Jackson uses the standard collection deserialiser for Java collection types which knows nothing about ItemList properties.
It is possible to make it work but not in a very elegant way. You need to configure ObjectMapper to replace the default collection deserialiser on a bean deserialiser created manually for the corresponding type. I have written an example that does this in BeanDeserializerModifier for all the classes annotated with a custom annotation.
Note that I have to override ObjectMapper to get access to a protected method createDeserializationContext of ObjectMapper to create a proper deserialisation context since the bean modifier does not have access to it.
Here is the code:
public class JacksonCustomList {
public static final String JSON = "{\n" +
" \"item\": [\n" +
" { \"foo\": 1 },\n" +
" { \"foo\": 2 }\n" +
" ]\n" +
"} ";
#Retention(RetentionPolicy.RUNTIME)
public static #interface PreferBeanDeserializer {
}
public static class Item {
public int foo;
#Override
public String toString() {
return String.valueOf(foo);
}
}
#PreferBeanDeserializer
public static class ItemList extends ArrayList<Item> {
#JsonProperty("item")
public List<Item> items;
#Override
public String toString() {
return items.toString();
}
}
public static class Modifier extends BeanDeserializerModifier {
private final MyObjectMapper mapper;
public Modifier(final MyObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public JsonDeserializer<?> modifyCollectionDeserializer(
final DeserializationConfig config,
final CollectionType type,
final BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
if (type.getRawClass().getAnnotation(PreferBeanDeserializer.class) != null) {
DeserializationContext context = mapper.createContext(config);
try {
return context.getFactory().createBeanDeserializer(context, type, beanDesc);
} catch (JsonMappingException e) {
throw new IllegalStateException(e);
}
}
return super.modifyCollectionDeserializer(config, type, beanDesc, deserializer);
}
}
public static class MyObjectMapper extends ObjectMapper {
public DeserializationContext createContext(final DeserializationConfig cfg) {
return super.createDeserializationContext(getDeserializationContext().getParser(), cfg);
}
}
public static void main(String[] args) throws IOException {
final MyObjectMapper mapper = new MyObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new Modifier(mapper));
mapper.registerModule(module);
System.out.println(mapper.readValue(JSON, ItemList.class));
}
}

If you consider the item property to be the root value, you can than change your ItemList class as follows, using the #JsonRootName annotation:
#JsonRootName("item")
public class ItemList implements Collection<Item>, Iterable<Item> {
private List<Item> items = new ArrayList<>();
public Item get(int position) {
return items.get(position);
}
// implemented methods deferring to delegate
// ...
}
If you then activate the UNWRAP_ROOT_VALUE deserialization feature, things work as expected:
String json = "{\"item\": [{\"foo\": 1}, {\"foo\": 2}]}";
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.reader(ItemList.class);
ItemList itemList = reader
.with(DeserializationFeature.UNWRAP_ROOT_VALUE)
.readValue(json);
Serialization works equally well, with the WRAP_ROOT_VALUE serialization feature enabled:
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer();
Item item1 = new Item();
item1.setFoo(1);
Item item2 = new Item();
item2.setFoo(2);
ItemList itemList = new ItemList();
itemList.add(item1);
itemList.add(item2);
String json = writer
.with(SerializationFeature.WRAP_ROOT_VALUE)
.writeValueAsString(itemList);
// json contains {"item":[{"foo":1},{"foo":2}]}
This solution will obviously not suffice if your ItemList contains additional properties (other than the actual list) that will also need to be serialized/deserialized.

Related

Ignore properties marked with specific annotation

I have ObjectMapper instance:
ObjectMapper mapper = new ObjectMapper();
In runtime want to serialize instance of class. What is the class the program doesn't known. It's object instance of parameterized type T.
How to ignore all properties (fields and getters) which marked specified annotation (javax.persistence.Id) ?
Example:
public static class PojoTest {
#Id
public String idTest;
public String id;
}
public void serialize(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
// TODO ignore property mark #Id annotation
Map<Object, Object> map = objectMapper.convertValue(object, Map.class);
assertFalse(map.containsKey("idTest"));
}
public void test() {
PojoTest pojoTest = new PojoTest();
pojoTest.id = "foo";
pojoTest.idTest = "bar";
serialize(pojoTest);
}
You can implement a new com.fasterxml.jackson.databind.AnnotationIntrospector class where you can extend hasIgnoreMarker method:
static class IdIgnoreAnnotationIntrospector extends AnnotationIntrospector {
#Override
public Version version() {
return new Version(1,0,0,"Ignore #Id", "group.id", "artifact.id");
}
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return hasIdAnnotation(m);
}
boolean hasIdAnnotation(AnnotatedMember member) {
return member.getAnnotation(Id.class) != null;
}
}
Now you need to register this introspector:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(new JacksonAnnotationIntrospector(), new IdIgnoreAnnotationIntrospector()));
Now you can ignore all fields marked with #Id annotation.

Enable Jackson Deserialization of Empty Objects to Null

I was asked to change our jackson mapping configuration so that each empty object we deserialize (from JSON) is going to be deserialized as null.
The problem is that I'm struggling to do it, but without any luck. Here is a sample of our ObjectMapper configuration (and example):
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ISO_DATE_TIME));
javaTimeModule.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
mapper.registerModule(javaTimeModule);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
warmupMapper(mapper);
return mapper;
I thought about something like adding:
mapper.configure(
DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
but it just works on strings.
I'm afraid that using a custom deserializer will not help me, because I'm writing a generic (for all objects) mapper. So I probably need something like a delegator or a post process deserialization method.
So for json like "" or {} I expect to be converted to null in java (and not to empty string or Object instance).
What is a empty object for you? A object with null value fields? A object with no fields? You can create a custom to check the nodes and deserialize how you want. I see no problem to use it in a generic way.
I did a little example:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.Objects;
public class DeserializerExample<T> extends StdDeserializer<T> {
private final ObjectMapper defaultMapper;
public DeserializerExample(Class<T> clazz) {
super(clazz);
defaultMapper = new ObjectMapper();
}
#Override
public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
System.out.println("Deserializing...");
JsonNode node = jp.getCodec().readTree(jp);
for (JsonNode jsonNode : node) {
if (!jsonNode.isNull()) {
return defaultMapper.treeToValue(node, (Class<T>) getValueClass());
}
}
return null;
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new DeserializerExample(Person.class));
mapper.registerModule(module);
Person person = mapper.readValue("{\"id\":1, \"name\":\"Joseph\"}", Person.class);
Person nullPerson = mapper.readValue("{\"id\":null, \"name\":null}", Person.class);
System.out.println("Is null: " + Objects.isNull(person));
System.out.println("Is null: " + Objects.isNull(nullPerson));
}
}
The only way to do this is to use a custom deserializer:
class CustomDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.readValueAsTree();
if (node.asText().isEmpty()) {
return null;
}
return node.toString();
}
}
Then do:
class EventBean {
public Long eventId;
public String title;
#JsonDeserialize(using = CustomDeserializer.class)
public String location;
}
This solution courtesy of Sach141 on this question.
I had the same problem.
I hava a City class and sometimes I recive 'city':{} from a web service request.
So, the standard serializer create a new City with all empty field.
I created a custom deserializer in this way
public class CityJsonDeSerializer extends StdDeserializer<City> {
#Override
public City deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
if(node.isNull() || node.asText().isEmpty()|| node.size()==0)
return null;
City city = new City();
... // set all fields
return city;
}
}
The if check the conditions:
'city' : null
'city' : ''
'city' : '{}'
and if it's true, the deserializer returns null.
Another approach is to use a com.fasterxml.jackson.databind.util.Converter<IN,OUT>, which is essentially a postprocessor for deserialization.
Imagine we have a class:
public class Person {
public String id;
public String name;
}
Now imagine we want to deserialize an empty JSON object {} as null, rather than a Person with null values for id and name. We can create the following Converter:
public PersonConverter implements Converter<Person,Person> {
#Override
public Person convert(Person p) {
return isEmpty(p) ? null : value;
}
private static boolean isEmpty(Person p) {
if(p == null) {
return true;
}
if(Optional.ofNullable(p.id).orElse("").isEmpty() &&
Optional.ofNullable(p.name).orElse("").isEmpty()) {
return true;
}
return false;
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructType(Person.class);
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructType(Person.class);
}
}
Note that we have to handle the blank String case because that is (counter-intuitively) the default value assigned to properties not given in JSON, as opposed to null.
Given the converter, we can then annotate our original Person class:
#JsonDeserialize(converter=PersonConverter.class)
public class Person {
public String id;
public String name;
}
The benefit of this approach is that you don't have to think or care about deserialization at all; you're simply deciding what to do with the deserialized object after it's deserialized. And there are many other transformations you can do with a Converter, too. But this works nicely for nullifying "empty" values.

Handle Polymorphic with StdDeserializer Jackson 2.5

I have three classes which inherits from a super class (SensorData)
#JsonDeserialize(using = SensorDataDeserializer.class)
public abstract class SensorData {
}
public class HumiditySensorData extends SensorData {
}
public class LuminositySensorData extends SensorData {
}
public class TemperatureSensorData extends SensorData {
}
I want convert a json input into one of this classes depending on a parameter. I'm trying to use Jackson StdDeserializer and I create a custom deserializer
#Component
public class SensorDataDeserializer extends StdDeserializer<SensorData> {
private static final long serialVersionUID = 3625068688939160875L;
#Autowired
private SensorManager sensorManager;
private static final String discriminator = "name";
public SensorDataDeserializer() {
super(SensorData.class);
SpringBeanProvider.getInstance().autowireBean(this);
}
#Override
public SensorData deserialize(JsonParser parser,
DeserializationContext context) throws IOException,
JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(parser);
ObjectNode sensor = (ObjectNode) root.get("data");
String type = root.get(discriminator).asText();
Class<? extends SensorData> clazz = this.sensorManager
.getCachedSensorsMap().get(type).sensorDataClass();
if (clazz == null) {
// TODO should throw exception
return null;
}
return mapper.readValue(sensor.traverse(), clazz);
}
}
My problem is that when I determine the correct type to mapping the concrete class, the mapper call again to the custom StdDeserializer. So I need a way
to broke the cycle when I have the correct type. The stacktrace is the next one
java.lang.NullPointerException
at com.hp.psiot.mapping.SensorDataDeserializer.deserialize(SensorDataDeserializer.java:38)
at com.hp.psiot.mapping.SensorDataDeserializer.deserialize(SensorDataDeserializer.java:1)
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3532)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1868)
at com.hp.psiot.mapping.SensorDataDeserializer.deserialize(SensorDataDeserializer.java:47)
at com.hp.psiot.mapping.SensorDataDeserializer.deserialize(SensorDataDeserializer.java:1)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3560)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2660)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:205)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:200)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters (AbstractMessageConverterMethodArgumentResolver.java:138)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:184)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:105)
An example of input
{
"name":"temperature",
"data": {
"value":20
}
}
I only include the stacktrace to show that the mapper is calling again to the deserializer. The reason for the nullPointerException is that when the second the ObjectMapper is called the input is
"value":20
So, An exception is threw because we don't have the information to determine the type and it doesn't check if the input is correct
I want to avoid using JsonSubTypes and JsonTypeInfo if it's posible.
Thanks in advance!
Partial solution
In my case the SensorData is wrapped in other class (ServiceData)
class ServiceData {
#JsonDeserialize(using = SensorDataDeserializer.class)
List<SensorData> sensors;
}
So, I get rid of JsonDeserializer in SensorData class and put it in the field avoiding the cycle. The solution isn't the best, but in my case it helps me. But in the case that the class isn't wrapped in another one we still have the same problem.
Note that if you have a Collection and you annotate with JsonDeserialize that field you have to handle all the collection. Here is the modification
in my case
#Component
public class SensorDataDeserializer extends StdDeserializer<List<SensorData>> {
private static final long serialVersionUID = 3625068688939160875L;
#Autowired
private SensorManager sensorManager;
private static final String discriminator = "name";
public SensorDataDeserializer() {
super(SensorData.class);
SpringBeanProvider.getInstance().autowireBean(this);
}
#Override
public List<SensorData> deserialize(JsonParser parser,
DeserializationContext context) throws IOException,
JsonProcessingException {
try {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
ArrayNode root = (ArrayNode) mapper.readTree(parser);
int size = root.size();
List<SensorData> sensors = new ArrayList<SensorData>();
for (int i = 0; i < size; ++i) {
ObjectNode sensorHead = (ObjectNode) root.get(i);
ObjectNode sensorData = (ObjectNode) sensorHead.get("data");
String tag = sensorHead.get(discriminator).asText();
Class<? extends SensorData> clazz = this.sensorManager
.getCachedSensorsMap().get(tag).sensorDataClass();
if (clazz == null) {
throw new InvalidJson("unbound sensor");
}
SensorData parsed = mapper.readValue(sensorData.traverse(),
clazz);
if (parsed == null) {
throw new InvalidJson("unbound sensor");
}
sensors.add(parsed);
}
return sensors;
} catch (Throwable e) {
throw new InvalidJson("invalid data");
}
}
}
Hope it helps someone :)
Why don't you just use #JsonTypeInfo? Polymorphic handling is the specific use case for it.
In this case, you would want to use something like:
#JsonTypeInfo(use=Id.NAME, include=As.PROPERTY, property="name")
#JsonSubTypes({ HumiditySensorData.class, ... }) // or register via mapper
public abstract class SensorData { ... }
#JsonTypeName("temperature")
public class TemperaratureSensorData extends SensorData {
public TemperaratureSensorData(#JsonProperty("data") JsonNode data) {
// extract pieces out
}
}
which would handle resolution from 'name' into sub-type, bind contents of 'data' as JsonNode (or, if you prefer can use Map or Object or whatever type matches).

Jackson custom deserialization for polymorphic objects

I looked in many questions related to this but i can't find this exact use case.
Assumption here is that i am using Java Jackson library.
I have the following class hierarchy:
public class Event {
#JsonProperty("si")
String sessionId;
#JsonProperty("eventType")
String eventType;
...
}
#JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class InitEvent extends Event
{
#JsonProperty("pm")
Params params;
public Params getParams()
{
return params;
}
.....
}
#JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class RecoEvent extends Event
{
#JsonProperty("ti")
String targetId;
#JsonProperty("tt")
int targetType;
public String getTargetId()
{
return targetId;
}
....
}
The rule of deserialization is:
If eventType == 0 then deserialize to a InitEvent
If eventType == 0 then deserialize to a RecoEvent
Out of the box the Jackson deserialization will not work because it does not know which class to deserialize with. One way to handle that is by doing as follows on the base class:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
#JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class Event
The problem of this solution is assumes the client will serialize with the same mapper since the #class element needs to be present now in the JSON.
My client will not send the extra #class element in the incoming JSON.
What is the required solution?
How I could write a custom deserializer that picks the right derived class based on the eventType value?
Thx in advance
At the end I had to write my own deserializer.
This works in 2 steps, first implement the custom deserializer and then register it.
The implementation works as follows:
public class EvtListDeserializer extends JsonDeserializer<EventList>
{
private static ObjectMapper mapper = new ObjectMapper();
#Override
public EventList deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException
{
EventList eventList = new EventList();
List<Event> listOfEvents = new ArrayList<Event>();
ObjectCodec oc = jsonParser.getCodec();
JsonNode eventListNode = oc.readTree(jsonParser);
ArrayNode node = (ArrayNode) eventListNode.get("evt");
Iterator<JsonNode> events = node.elements();
for (; events.hasNext(); )
{
JsonNode eventNode = events.next();
int eventType = eventNode.get("type").asInt();
Event event;
if (eventType == EventTypes.MoveEvent.getValue())
{
event = mapper.readValue(eventNode.toString(), MoveEvent.class);
}
else if (eventType == EventTypes.CopyEvent.getValue())
{
event = mapper.readValue(eventNode.toString(), CopyEvent.class);
}
else
{
throw new InvalidEventTypeException("Invalid event type:" + eventType);
}
listOfEvents.add(event);
}
eventList.setEvents(listOfEvents);
return eventList;
}
}
Then you just register it as follows in the class that uses your mapper:
public void init()
{
SimpleModule usageModule = new SimpleModule().addDeserializer(EventList.class, new EvtListDeserializer());
mapper.registerModule(usageModule);
}
That works perfectly.

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

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

Categories

Resources