| package com.fasterxml.jackson.databind.deser; |
| |
| import java.io.IOException; |
| import java.util.*; |
| |
| import com.fasterxml.jackson.core.*; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
| import com.fasterxml.jackson.databind.deser.std.StdDeserializer; |
| |
| /** |
| * This unit test suite tests use of "value" Annotations; |
| * annotations that define actual type (Class) to use for |
| * deserialization. |
| */ |
| public class TestValueAnnotations |
| extends BaseMapTest |
| { |
| /* |
| /********************************************************** |
| /* Annotated root classes for @JsonDeserialize#as |
| /********************************************************** |
| */ |
| |
| @JsonDeserialize(using=RootStringDeserializer.class) |
| interface RootString { |
| public String contents(); |
| } |
| |
| static class RootStringImpl implements RootString |
| { |
| final String _contents; |
| |
| public RootStringImpl(String x) { _contents = x; } |
| |
| @Override |
| public String contents() { return _contents; } |
| public String contents2() { return _contents; } |
| } |
| |
| @JsonDeserialize(as=RootInterfaceImpl.class) |
| interface RootInterface { |
| public String getA(); |
| } |
| |
| static class RootInterfaceImpl implements RootInterface { |
| public String a; |
| |
| public RootInterfaceImpl() { } |
| |
| @Override |
| public String getA() { return a; } |
| } |
| |
| @SuppressWarnings("serial") |
| @JsonDeserialize(contentAs=RootStringImpl.class) |
| static class RootMap extends HashMap<String,RootStringImpl> { } |
| |
| @SuppressWarnings("serial") |
| @JsonDeserialize(contentAs=RootStringImpl.class) |
| static class RootList extends LinkedList<RootStringImpl> { } |
| |
| @SuppressWarnings("serial") |
| static class RootStringDeserializer |
| extends StdDeserializer<RootString> |
| { |
| public RootStringDeserializer() { super(RootString.class); } |
| |
| @Override |
| public RootString deserialize(JsonParser p, DeserializationContext ctxt) |
| throws IOException |
| { |
| if (p.hasToken(JsonToken.VALUE_STRING)) { |
| return new RootStringImpl(p.getText()); |
| } |
| return (RootString) ctxt.handleUnexpectedToken(_valueClass, p); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Annotated helper classes for @JsonDeserialize#as |
| /********************************************************** |
| */ |
| |
| /* Class for testing valid {@link JsonDeserialize} annotation |
| * with 'as' parameter to define concrete class to deserialize to |
| */ |
| final static class CollectionHolder |
| { |
| Collection<String> _strings; |
| |
| /* Default for 'Collection' would probably be ArrayList or so; |
| * let's try to make it a TreeSet instead. |
| */ |
| @JsonDeserialize(as=TreeSet.class) |
| public void setStrings(Collection<String> s) |
| { |
| _strings = s; |
| } |
| } |
| |
| /* Another class for testing valid {@link JsonDeserialize} annotation |
| * with 'as' parameter to define concrete class to deserialize to |
| */ |
| final static class MapHolder |
| { |
| // Let's also coerce numbers into Strings here |
| Map<String,String> _data; |
| |
| /* Default for 'Collection' would be HashMap, |
| * let's try to make it a TreeMap instead. |
| */ |
| @JsonDeserialize(as=TreeMap.class) |
| public void setStrings(Map<String,String> s) |
| { |
| _data = s; |
| } |
| } |
| |
| /* Another class for testing valid {@link JsonDeserialize} annotation |
| * with 'as' parameter, but with array |
| */ |
| final static class ArrayHolder |
| { |
| String[] _strings; |
| |
| @JsonDeserialize(as=String[].class) |
| public void setStrings(Object[] o) |
| { |
| // should be passed instances of proper type, as per annotation |
| _strings = (String[]) o; |
| } |
| } |
| |
| /* Another class for testing broken {@link JsonDeserialize} annotation |
| * with 'as' parameter; one with incompatible type |
| */ |
| final static class BrokenCollectionHolder |
| { |
| @JsonDeserialize(as=String.class) // not assignable to Collection |
| public void setStrings(Collection<String> s) { } |
| } |
| |
| /* |
| /********************************************************** |
| /* Annotated helper classes for @JsonDeserialize.keyAs |
| /********************************************************** |
| */ |
| |
| final static class StringWrapper |
| { |
| final String _string; |
| |
| public StringWrapper(String s) { _string = s; } |
| } |
| |
| final static class MapKeyHolder |
| { |
| Map<Object, String> _map; |
| |
| @JsonDeserialize(keyAs=StringWrapper.class) |
| public void setMap(Map<Object,String> m) |
| { |
| // type should be ok, but no need to cast here (won't matter) |
| _map = m; |
| } |
| } |
| |
| final static class BrokenMapKeyHolder |
| { |
| // Invalid: Integer not a sub-class of String |
| @JsonDeserialize(keyAs=Integer.class) |
| public void setStrings(Map<String,String> m) { } |
| } |
| |
| /* |
| /********************************************************** |
| /* Annotated helper classes for @JsonDeserialize#contentAs |
| /********************************************************** |
| */ |
| |
| final static class ListContentHolder |
| { |
| List<?> _list; |
| |
| @JsonDeserialize(contentAs=StringWrapper.class) |
| public void setList(List<?> l) { |
| _list = l; |
| } |
| } |
| |
| final static class InvalidContentClass |
| { |
| /* Such annotation not allowed, since it makes no sense; |
| * non-container classes have no contents to annotate (but |
| * note that it is possible to first use @JsonDesiarialize.as |
| * to mark Object as, say, a List, and THEN use |
| * @JsonDeserialize.contentAs!) |
| */ |
| @JsonDeserialize(contentAs=String.class) |
| public void setValue(Object x) { } |
| } |
| |
| final static class ArrayContentHolder |
| { |
| Object[] _data; |
| |
| @JsonDeserialize(contentAs=Long.class) |
| public void setData(Object[] o) |
| { // should have proper type, but no need to coerce here |
| _data = o; |
| } |
| } |
| |
| final static class MapContentHolder |
| { |
| Map<Object,Object> _map; |
| |
| @JsonDeserialize(contentAs=Integer.class) |
| public void setMap(Map<Object,Object> m) |
| { |
| _map = m; |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Test methods for @JsonDeserialize#as |
| /********************************************************** |
| */ |
| |
| public void testOverrideClassValid() throws Exception |
| { |
| ObjectMapper m = new ObjectMapper(); |
| CollectionHolder result = m.readValue |
| ("{ \"strings\" : [ \"test\" ] }", CollectionHolder.class); |
| |
| Collection<String> strs = result._strings; |
| assertEquals(1, strs.size()); |
| assertEquals(TreeSet.class, strs.getClass()); |
| assertEquals("test", strs.iterator().next()); |
| } |
| |
| public void testOverrideMapValid() throws Exception |
| { |
| ObjectMapper m = new ObjectMapper(); |
| // note: expecting conversion from number to String, as well |
| MapHolder result = m.readValue |
| ("{ \"strings\" : { \"a\" : 3 } }", MapHolder.class); |
| |
| Map<String,String> strs = result._data; |
| assertEquals(1, strs.size()); |
| assertEquals(TreeMap.class, strs.getClass()); |
| String value = strs.get("a"); |
| assertEquals("3", value); |
| } |
| |
| public void testOverrideArrayClass() throws Exception |
| { |
| ObjectMapper m = new ObjectMapper(); |
| ArrayHolder result = m.readValue |
| ("{ \"strings\" : [ \"test\" ] }", ArrayHolder.class); |
| |
| String[] strs = result._strings; |
| assertEquals(1, strs.length); |
| assertEquals(String[].class, strs.getClass()); |
| assertEquals("test", strs[0]); |
| } |
| |
| public void testOverrideClassInvalid() throws Exception |
| { |
| // should fail due to incompatible Annotation |
| try { |
| BrokenCollectionHolder result = new ObjectMapper().readValue |
| ("{ \"strings\" : [ ] }", BrokenCollectionHolder.class); |
| fail("Expected a failure, but got results: "+result); |
| } catch (JsonMappingException jme) { |
| verifyException(jme, "not subtype of"); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Test methods for @JsonDeserialize#as used for root values |
| /********************************************************** |
| */ |
| |
| public void testRootInterfaceAs() throws Exception |
| { |
| RootInterface value = new ObjectMapper().readValue("{\"a\":\"abc\" }", RootInterface.class); |
| assertTrue(value instanceof RootInterfaceImpl); |
| assertEquals("abc", value.getA()); |
| } |
| |
| public void testRootInterfaceUsing() throws Exception |
| { |
| RootString value = new ObjectMapper().readValue("\"xxx\"", RootString.class); |
| assertTrue(value instanceof RootString); |
| assertEquals("xxx", value.contents()); |
| } |
| |
| public void testRootListAs() throws Exception |
| { |
| RootMap value = new ObjectMapper().readValue("{\"a\":\"b\"}", RootMap.class); |
| assertEquals(1, value.size()); |
| Object v2 = value.get("a"); |
| assertEquals(RootStringImpl.class, v2.getClass()); |
| assertEquals("b", ((RootString) v2).contents()); |
| } |
| |
| public void testRootMapAs() throws Exception |
| { |
| RootList value = new ObjectMapper().readValue("[ \"c\" ]", RootList.class); |
| assertEquals(1, value.size()); |
| Object v2 = value.get(0); |
| assertEquals(RootStringImpl.class, v2.getClass()); |
| assertEquals("c", ((RootString) v2).contents()); |
| } |
| |
| /* |
| /********************************************************** |
| /* Test methods for @JsonDeserialize#keyAs |
| /********************************************************** |
| */ |
| |
| @SuppressWarnings("unchecked") |
| public void testOverrideKeyClassValid() throws Exception |
| { |
| ObjectMapper m = new ObjectMapper(); |
| MapKeyHolder result = m.readValue("{ \"map\" : { \"xxx\" : \"yyy\" } }", MapKeyHolder.class); |
| Map<StringWrapper, String> map = (Map<StringWrapper,String>)(Map<?,?>)result._map; |
| assertEquals(1, map.size()); |
| Map.Entry<StringWrapper, String> en = map.entrySet().iterator().next(); |
| |
| StringWrapper key = en.getKey(); |
| assertEquals(StringWrapper.class, key.getClass()); |
| assertEquals("xxx", key._string); |
| assertEquals("yyy", en.getValue()); |
| } |
| |
| public void testOverrideKeyClassInvalid() throws Exception |
| { |
| // should fail due to incompatible Annotation |
| try { |
| BrokenMapKeyHolder result = new ObjectMapper().readValue |
| ("{ \"123\" : \"xxx\" }", BrokenMapKeyHolder.class); |
| fail("Expected a failure, but got results: "+result); |
| } catch (JsonMappingException jme) { |
| verifyException(jme, "not subtype of"); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Test methods for @JsonDeserialize#contentAs |
| /********************************************************** |
| */ |
| |
| @SuppressWarnings("unchecked") |
| public void testOverrideContentClassValid() throws Exception |
| { |
| ObjectMapper m = new ObjectMapper(); |
| ListContentHolder result = m.readValue("{ \"list\" : [ \"abc\" ] }", ListContentHolder.class); |
| List<StringWrapper> list = (List<StringWrapper>)result._list; |
| assertEquals(1, list.size()); |
| Object value = list.get(0); |
| assertEquals(StringWrapper.class, value.getClass()); |
| assertEquals("abc", ((StringWrapper) value)._string); |
| } |
| |
| public void testOverrideArrayContents() throws Exception |
| { |
| ObjectMapper m = new ObjectMapper(); |
| ArrayContentHolder result = m.readValue("{ \"data\" : [ 1, 2, 3 ] }", |
| ArrayContentHolder.class); |
| Object[] data = result._data; |
| assertEquals(3, data.length); |
| assertEquals(Long[].class, data.getClass()); |
| assertEquals(1L, data[0]); |
| assertEquals(2L, data[1]); |
| assertEquals(3L, data[2]); |
| } |
| |
| public void testOverrideMapContents() throws Exception |
| { |
| ObjectMapper m = new ObjectMapper(); |
| MapContentHolder result = m.readValue("{ \"map\" : { \"a\" : 9 } }", |
| MapContentHolder.class); |
| Map<Object,Object> map = result._map; |
| assertEquals(1, map.size()); |
| Object ob = map.values().iterator().next(); |
| assertEquals(Integer.class, ob.getClass()); |
| assertEquals(Integer.valueOf(9), ob); |
| } |
| } |