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