Reputation: 876
I have a problem with wrong objects in lists. For instance I've a JSON model:
{
"items": [
{
"id": 1,
"name": "Item1"
},
{
"id": 2,
"name": "Item2"
},
{
"id": [],
"name": "Item3"
}
]
}
and two POJO
data class BadList(val items: List<BadItem>)
data class BadItem(val id: Int, val name: String)
Of course, when the parser stumbles upon a third element I get the exception
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.Integer out of START_ARRAY token
at [Source: {"items":[{"id":1,"name":"Item1"},{"id":2,"name":"Item2"},{"id":[],"name":"Item3"}]}; line: 1, column: 19] (through reference chain: my.package.BadList["items"]->java.util.ArrayList[2]->my.package.BadItem["id"])
Who knows how to get around this? I want to skip that wrong item.
Upvotes: 0
Views: 4532
Reputation: 10485
You can use a "HidableSerializer" for this and check the data during serialization
1. Create a IHidable interface
The interface has a isHidden method which is called during serialization
package ch.hasselba.jackson.test;
public interface IHidable {
public boolean isHidden();
}
2. Change your BadItem class
Add the interface and change the setter of id. When property id is deserialized, it is tested if it is an Integer. If not, the item is marked as bad.
package ch.hasselba.jackson.test;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties( {"hidden"} )
public class BadItem implements IHidable{
private Integer id;
public String name;
private boolean isBadItem;
public Integer getId(){
return id;
}
public void setId(Object value){
if( value instanceof Integer ){
this.id = (Integer) value;
}else{
this.isBadItem = true;
}
}
public boolean isHidden() {
return isBadItem;
}
}
3. Create a HidableSerializer
package ch.hasselba.jackson.test;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
@SuppressWarnings("serial")
public class HidableSerializer<T> extends StdSerializer<T> {
private JsonSerializer<T> defaultSerializer;
protected HidableSerializer(Class<T> t) {
super(t);
}
public JsonSerializer<T> getDefaultSerializer() {
return defaultSerializer;
}
public void setDefaultSerializer(JsonSerializer<T> defaultSerializer) {
this.defaultSerializer = defaultSerializer;
}
@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);
}
}
4. Register the HidableSerializer and that's it
package ch.hasselba.jackson.test;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
public class Demo {
@SuppressWarnings("serial")
public static void main(String[] args) {
// register the HidableSerializer
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_EMPTY);
mapper.registerModule(new SimpleModule() {
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new BeanSerializerModifier() {
@Override
public JsonSerializer<?> modifySerializer(
SerializationConfig config, BeanDescription desc, JsonSerializer<?> serializer) {
if (BadItem.class.isAssignableFrom(desc.getBeanClass())) {
HidableSerializer ser = new HidableSerializer(BadItem.class);
ser.setDefaultSerializer(serializer);
return ser;
}
return serializer;
}
});
}
});
String content = "{ \"items\": [ { \"id\": 1, \"name\": \"Item1\" }, { \"id\": 2, \"name\": \"Item2\" }, { \"id\":[], \"name\": \"Item3\" } ]}";
// build the Object
BadList test = null;
try {
test = mapper.readValue(content, BadList.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();
}
// print the result
System.out.println( data );
}
}
When changing the id "[]" to an Integer value, the Item is displayed, otherwise it is empty.
The result:
{"items":[{"id":1,"name":"Item1"},{"id":2,"name":"Item2"}]}
Upvotes: 1
Reputation: 30839
You can write a custom deserializer
and implement deserialization logic in it, e.g.:
class ItemIdDeserialiser extends JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Object value = p.getCurrentValue();
//Check if it's Integer
if(value instanceof Integer){
return (Integer) value;
}
return null; //Or return first element if it's a non empty list
}
}
Once this is done, you can annotate the field with @JsonDeserialise
to instruct jackson to use your class, e.g.:
class Item {
@JsonDeserialize(using = ItemIdDeserialiser.class)
private Integer id;
}
Update
If you just want to ignore the field in serialization/deserialization then you can annotate it with @JsonIgnore
, e.g.
class Item {
@JsonIgnore
private Integer id;
}
Or even better, remove id
from pojo and add @JsonIgnoreProperties
on the class, e.g.:
@JsonIgnoreProperties(ignoreUnknown = true)
class Item {
}
It will automatically ignore the properties which are present in json
but not found in class.
Upvotes: 2