package com.fasterxml.jackson.databind.deser;
import java.io.*;
import java.lang.annotation.*;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.*;
import com.fasterxml.jackson.databind.deser.std.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.util.StdConverter;
/**
* Test to check that customizations work as expected.
*/
@SuppressWarnings("serial")
public class TestCustomDeserializers
extends BaseMapTest
{
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
static class DummyDeserializer<T>
extends StdDeserializer<T>
{
final T value;
public DummyDeserializer(T v, Class<T> cls) {
super(cls);
value = v;
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
// need to skip, if structured...
p.skipChildren();
return value;
}
}
static class TestBeans {
public List<TestBean> beans;
}
static class TestBean {
public CustomBean c;
public String d;
}
@JsonDeserialize(using=CustomBeanDeserializer.class)
static class CustomBean {
protected final int a, b;
public CustomBean(int a, int b) {
this.a = a;
this.b = b;
}
}
static class CustomBeanDeserializer extends JsonDeserializer<CustomBean>
{
@Override
public CustomBean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
int a = 0, b = 0;
JsonToken t = p.getCurrentToken();
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
} else if (t != JsonToken.FIELD_NAME) {
throw new Error();
}
while(t == JsonToken.FIELD_NAME) {
final String fieldName = p.getCurrentName();
t = p.nextToken();
if (t != JsonToken.VALUE_NUMBER_INT) {
throw new JsonParseException(p, "expecting number got "+ t);
}
if (fieldName.equals("a")) {
a = p.getIntValue();
} else if (fieldName.equals("b")) {
b = p.getIntValue();
} else {
throw new Error();
}
t = p.nextToken();
}
return new CustomBean(a, b);
}
}
public static class Immutable {
protected int x, y;
public Immutable(int x0, int y0) {
x = x0;
y = y0;
}
}
public static class CustomKey {
private final int id;
public CustomKey(int id) {this.id = id;}
public int getId() { return id; }
}
public static class Model
{
protected final Map<CustomKey, String> map;
@JsonCreator
public Model(@JsonProperty("map") @JsonDeserialize(keyUsing = CustomKeyDeserializer.class) Map<CustomKey, String> map)
{
this.map = new HashMap<CustomKey, String>(map);
}
@JsonProperty
@JsonSerialize(keyUsing = CustomKeySerializer.class)
public Map<CustomKey, String> getMap() {
return map;
}
}
static class CustomKeySerializer extends JsonSerializer<CustomKey> {
@Override
public void serialize(CustomKey value, JsonGenerator g, SerializerProvider provider) throws IOException {
g.writeFieldName(String.valueOf(value.getId()));
}
}
static class CustomKeyDeserializer extends KeyDeserializer {
@Override
public CustomKey deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return new CustomKey(Integer.valueOf(key));
}
}
// [databind#375]
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface Negative { }
static class Bean375Wrapper {
@Negative
public Bean375Outer value;
}
static class Bean375Outer {
protected Bean375Inner inner;
public Bean375Outer(Bean375Inner v) { inner = v; }
}
static class Bean375Inner {
protected int x;
public Bean375Inner(int x) { this.x = x; }
}
static class Bean375OuterDeserializer extends StdDeserializer<Bean375Outer>
implements ContextualDeserializer
{
protected BeanProperty prop;
protected Bean375OuterDeserializer() { this(null); }
protected Bean375OuterDeserializer(BeanProperty p) {
super(Bean375Outer.class);
prop = p;
}
@Override
public Bean375Outer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
Object ob = ctxt.readPropertyValue(p, prop, Bean375Inner.class);
return new Bean375Outer((Bean375Inner) ob);
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
return new Bean375OuterDeserializer(property);
}
}
static class Bean375InnerDeserializer extends StdDeserializer<Bean375Inner>
implements ContextualDeserializer
{
protected boolean negative;
protected Bean375InnerDeserializer() { this(false); }
protected Bean375InnerDeserializer(boolean n) {
super(Bean375Inner.class);
negative = n;
}
@Override
public Bean375Inner deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
int x = p.getIntValue();
if (negative) {
x = -x;
} else {
x += x;
}
return new Bean375Inner(x);
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
if (property != null) {
Negative n = property.getAnnotation(Negative.class);
if (n != null) {
return new Bean375InnerDeserializer(true);
}
}
return this;
}
}
// for [databind#631]
static class Issue631Bean
{
@JsonDeserialize(using=ParentClassDeserializer.class)
public Object prop;
}
static class ParentClassDeserializer
extends StdScalarDeserializer<Object>
{
protected ParentClassDeserializer() {
super(Object.class);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
Object parent = p.getCurrentValue();
String desc = (parent == null) ? "NULL" : parent.getClass().getSimpleName();
return "prop/"+ desc;
}
}
static class UCStringDeserializer extends StdDeserializer<String> {
public UCStringDeserializer() { super(String.class); }
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
return p.getText().toUpperCase();
}
}
static class DelegatingModuleImpl extends SimpleModule
{
public DelegatingModuleImpl() {
super("test", Version.unknownVersion());
}
@Override
public void setupModule(SetupContext context)
{
super.setupModule(context);
context.addBeanDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (deserializer.handledType() == String.class) {
JsonDeserializer<?> d = new MyStringDeserializer(deserializer);
// just for test coverage purposes...
if (d.getDelegatee() != deserializer) {
throw new Error("Can not access delegatee!");
}
return d;
}
return deserializer;
}
});
}
}
static class MyStringDeserializer extends DelegatingDeserializer
{
public MyStringDeserializer(JsonDeserializer<?> newDel) {
super(newDel);
}
@Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDel) {
return new MyStringDeserializer(newDel);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
Object ob = _delegatee.deserialize(p, ctxt);
return "MY:"+ob;
}
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
final ObjectMapper MAPPER = objectMapper();
public void testCustomBeanDeserializer() throws Exception
{
String json = "{\"beans\":[{\"c\":{\"a\":10,\"b\":20},\"d\":\"hello, tatu\"}]}";
TestBeans beans = MAPPER.readValue(json, TestBeans.class);
assertNotNull(beans);
List<TestBean> results = beans.beans;
assertNotNull(results);
assertEquals(1, results.size());
TestBean bean = results.get(0);
assertEquals("hello, tatu", bean.d);
CustomBean c = bean.c;
assertNotNull(c);
assertEquals(10, c.a);
assertEquals(20, c.b);
json = "{\"beans\":[{\"c\":{\"b\":3,\"a\":-4},\"d\":\"\"},"
+"{\"d\":\"abc\", \"c\":{\"b\":15}}]}";
beans = MAPPER.readValue(json, TestBeans.class);
assertNotNull(beans);
results = beans.beans;
assertNotNull(results);
assertEquals(2, results.size());
bean = results.get(0);
assertEquals("", bean.d);
c = bean.c;
assertNotNull(c);
assertEquals(-4, c.a);
assertEquals(3, c.b);
bean = results.get(1);
assertEquals("abc", bean.d);
c = bean.c;
assertNotNull(c);
assertEquals(0, c.a);
assertEquals(15, c.b);
}
// [Issue#87]: delegating deserializer
public void testDelegating() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(Immutable.class,
new StdDelegatingDeserializer<Immutable>(
new StdConverter<JsonNode, Immutable>() {
@Override
public Immutable convert(JsonNode value)
{
int x = value.path("x").asInt();
int y = value.path("y").asInt();
return new Immutable(x, y);
}
}
));
mapper.registerModule(module);
Immutable imm = mapper.readValue("{\"x\":3,\"y\":7}", Immutable.class);
assertEquals(3, imm.x);
assertEquals(7, imm.y);
}
// [databind#623]
public void testJsonNodeDelegating() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(Immutable.class,
new StdNodeBasedDeserializer<Immutable>(Immutable.class) {
@Override
public Immutable convert(JsonNode root, DeserializationContext ctxt) throws IOException {
int x = root.path("x").asInt();
int y = root.path("y").asInt();
return new Immutable(x, y);
}
});
mapper.registerModule(module);
Immutable imm = mapper.readValue("{\"x\":-10,\"y\":3}", Immutable.class);
assertEquals(-10, imm.x);
assertEquals(3, imm.y);
}
public void testIssue882() throws Exception
{
Model original = new Model(Collections.singletonMap(new CustomKey(123), "test"));
String json = MAPPER.writeValueAsString(original);
Model deserialized = MAPPER.readValue(json, Model.class);
assertNotNull(deserialized);
assertNotNull(deserialized.map);
assertEquals(1, deserialized.map.size());
}
// [#337]: convenience methods for custom deserializers to use
public void testContextReadValue() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(Bean375Outer.class, new Bean375OuterDeserializer());
module.addDeserializer(Bean375Inner.class, new Bean375InnerDeserializer());
mapper.registerModule(module);
// First, without property; doubles up value:
Bean375Outer outer = mapper.readValue("13", Bean375Outer.class);
assertEquals(26, outer.inner.x);
// then with property; should find annotation, turn negative
Bean375Wrapper w = mapper.readValue("{\"value\":13}", Bean375Wrapper.class);
assertNotNull(w.value);
assertNotNull(w.value.inner);
assertEquals(-13, w.value.inner.x);
}
// [#631]: "current value" access
public void testCurrentValueAccess() throws Exception
{
Issue631Bean bean = MAPPER.readValue(aposToQuotes("{'prop':'stuff'}"),
Issue631Bean.class);
assertNotNull(bean);
assertEquals("prop/Issue631Bean", bean.prop);
}
public void testCustomStringDeser() throws Exception
{
ObjectMapper mapper = new ObjectMapper().registerModule(
new SimpleModule().addDeserializer(String.class, new UCStringDeserializer())
);
assertEquals("FOO", mapper.readValue(quote("foo"), String.class));
StringWrapper sw = mapper.readValue("{\"str\":\"foo\"}", StringWrapper.class);
assertNotNull(sw);
assertEquals("FOO", sw.str);
}
public void testDelegatingDeserializer() throws Exception
{
ObjectMapper mapper = new ObjectMapper().registerModule(
new DelegatingModuleImpl());
String str = mapper.readValue(quote("foo"), String.class);
assertEquals("MY:foo", str);
}
}