Fixes to improve handling of typed deserialization for Object.class, abstract classes, interfaces.
diff --git a/src/mapper/java/org/codehaus/jackson/map/JsonSerializable.java b/src/mapper/java/org/codehaus/jackson/map/JsonSerializable.java
index 8a137e1..afa9fdb 100644
--- a/src/mapper/java/org/codehaus/jackson/map/JsonSerializable.java
+++ b/src/mapper/java/org/codehaus/jackson/map/JsonSerializable.java
@@ -13,6 +13,16 @@
* closely to Jackson API, and that it is often not necessary to do
* so -- if class is a bean, it can be serialized without
* implementing this interface.
+ *<p>
+ * NOTE: as of version 1.5, this interface is missing one crucial
+ * aspect, that of dealing with type information embedding.
+ * Because of this, this interface is deprecated, although will be
+ * fully supported for all 1.x releases.
+ *
+ * @see org.codehaus.jackson.map.JsonSerializableWithType
+ *
+ * @since 1.5
+ * @deprecated Use {@link JsonSerializableWithType} instead
*/
public interface JsonSerializable
{
diff --git a/src/mapper/java/org/codehaus/jackson/map/JsonSerializableWithType.java b/src/mapper/java/org/codehaus/jackson/map/JsonSerializableWithType.java
new file mode 100644
index 0000000..5e8f489
--- /dev/null
+++ b/src/mapper/java/org/codehaus/jackson/map/JsonSerializableWithType.java
@@ -0,0 +1,22 @@
+package org.codehaus.jackson.map;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+
+/**
+ * Interface that is to replace {@link JsonSerializable} to
+ * allow for dynamic type information embedding.
+ *
+ * @since 1.5
+ * @author tatu
+ */
+@SuppressWarnings("deprecation")
+public interface JsonSerializableWithType
+ extends JsonSerializable
+{
+ public void serializeWithType(JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonProcessingException;
+}
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
index 7a4b7ec..19311be 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
@@ -741,14 +741,36 @@
public static class CalendarDeserializer
extends StdScalarDeserializer<Calendar>
{
- public CalendarDeserializer() { super(Calendar.class); }
+ /**
+ * We may know actual expected type; if so, it will be
+ * used for instantiation.
+ */
+ Class<? extends Calendar> _calendarClass;
+
+ public CalendarDeserializer() { this(null); }
+ public CalendarDeserializer(Class<? extends Calendar> cc) {
+ super(Calendar.class);
+ _calendarClass = cc;
+ }
@Override
- public Calendar deserialize(JsonParser jp, DeserializationContext ctxt)
+ public Calendar deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
Date d = _parseDate(jp, ctxt);
- return (d == null) ? null : ctxt.constructCalendar(d);
+ if (d == null) {
+ return null;
+ }
+ if (_calendarClass == null) {
+ return ctxt.constructCalendar(d);
+ }
+ try {
+ Calendar c = _calendarClass.newInstance();
+ c.setTimeInMillis(d.getTime());
+ return c;
+ } catch (Exception e) {
+ throw ctxt.instantiationException(_calendarClass, e);
+ }
}
}
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializers.java b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializers.java
index 7d471ce..5cf0733 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializers.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializers.java
@@ -53,6 +53,11 @@
add(new DateDeserializer());
add(new StdDeserializer.SqlDateDeserializer());
add(new StdDeserializer.CalendarDeserializer());
+ /* 24-Jan-2010, tatu: When including type information, we may
+ * know that we specifically need GregorianCalendar...
+ */
+ add(new StdDeserializer.CalendarDeserializer(GregorianCalendar.class),
+ GregorianCalendar.class);
// Then other simple types:
add(new FromStringDeserializer.UUIDDeserializer());
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/BasicSerializerFactory.java b/src/mapper/java/org/codehaus/jackson/map/ser/BasicSerializerFactory.java
index 3052ced..7ae5f17 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/BasicSerializerFactory.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/BasicSerializerFactory.java
@@ -316,6 +316,7 @@
* a "primary" interface. Primary here is defined as the main function
* of the Object; as opposed to "add-on" functionality.
*/
+ @SuppressWarnings("deprecation")
public final JsonSerializer<?> findSerializerByPrimaryType(JavaType type, SerializationConfig config,
BasicBeanDescription beanDesc)
{
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/ContainerSerializerBase.java b/src/mapper/java/org/codehaus/jackson/map/ser/ContainerSerializerBase.java
index dcb7c94..d3d282f 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/ContainerSerializerBase.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/ContainerSerializerBase.java
@@ -18,9 +18,9 @@
protected TypeSerializer _valueTypeSerializer;
/*
- /*********************************************
- /* Construction
- /*********************************************
+ /*********************************************
+ /* Construction, initialization
+ /*********************************************
*/
protected ContainerSerializerBase(Class<T> t) {
@@ -40,11 +40,4 @@
public void setValueTypeSerializer(TypeSerializer valueTypeSer) {
_valueTypeSerializer = valueTypeSer;
}
-
- /*
- /*********************************************
- /* Construction
- /*********************************************
- */
-
}
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/ScalarSerializerBase.java b/src/mapper/java/org/codehaus/jackson/map/ser/ScalarSerializerBase.java
new file mode 100644
index 0000000..cd4279b
--- /dev/null
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/ScalarSerializerBase.java
@@ -0,0 +1,33 @@
+package org.codehaus.jackson.map.ser;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerationException;
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.TypeSerializer;
+
+public abstract class ScalarSerializerBase<T>
+ extends SerializerBase<T>
+{
+ protected ScalarSerializerBase(Class<T> t) {
+ super(t);
+ }
+
+ /**
+ * Default implementation will write type prefix, call regular serialization
+ * method (since assumption is that value itself does not need JSON
+ * Array or Object start/end markers), and then write type suffix.
+ * This should work for most cases; some sub-classes may want to
+ * change this behavior.
+ */
+ @Override
+ public void serializeWithType(T value, JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonGenerationException
+ {
+ typeSer.writeTypePrefixForScalar(value, jgen);
+ serialize(value, jgen, provider);
+ typeSer.writeTypeSuffixForScalar(value, jgen);
+ }
+}
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializers.java b/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializers.java
index fb4fd1e..34a477f 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializers.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializers.java
@@ -24,6 +24,35 @@
protected StdSerializers() { }
/*
+ /***********************************************************
+ /* Abstract base classes
+ /***********************************************************
+ */
+
+ /**
+ * Intermediate base class for limited number of scalar types
+ * that should never include type information. These are "native"
+ * types that are default mappings for corresponding JSON scalar
+ * types: String, Integer, Double and Boolean.
+ */
+ protected abstract static class NonTypedScalarSerializer<T>
+ extends ScalarSerializerBase<T>
+ {
+ protected NonTypedScalarSerializer(Class<T> t) {
+ super(t);
+ }
+
+ @Override
+ public final void serializeWithType(T value, JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonGenerationException
+ {
+ // no type info, just regular serialization
+ serialize(value, jgen, provider);
+ }
+ }
+
+ /*
/////////////////////////////////////////////////////////////////
// Concrete serializers, non-numeric primitives, Strings, Classes
/////////////////////////////////////////////////////////////////
@@ -31,10 +60,13 @@
/**
* Serializer used for primitive boolean, as well as java.util.Boolean
- * wrapper type
+ * wrapper type.
+ *<p>
+ * Since this is one of "native" types, no type information is ever
+ * included on serialization (unlike for most scalar types as of 1.5)
*/
public final static class BooleanSerializer
- extends SerializerBase<Boolean>
+ extends NonTypedScalarSerializer<Boolean>
{
/**
* Whether type serialized is primitive (boolean) or wrapper
@@ -71,9 +103,12 @@
/**
* This is the special serializer for regular {@link java.lang.String}s.
+ *<p>
+ * Since this is one of "native" types, no type information is ever
+ * included on serialization (unlike for most scalar types as of 1.5)
*/
public final static class StringSerializer
- extends SerializerBase<String>
+ extends NonTypedScalarSerializer<String>
{
public StringSerializer() { super(String.class); }
@@ -96,9 +131,16 @@
// Concrete serializers, numerics
////////////////////////////////////////////////////////////
*/
-
+
+ /**
+ * This is the special serializer for regular {@link java.lang.Integer}s
+ * (and primitive ints)
+ *<p>
+ * Since this is one of "native" types, no type information is ever
+ * included on serialization (unlike for most scalar types as of 1.5)
+ */
public final static class IntegerSerializer
- extends SerializerBase<Integer>
+ extends NonTypedScalarSerializer<Integer>
{
public IntegerSerializer() { super(Integer.class); }
@@ -123,7 +165,7 @@
* by calling {@link java.lang.Number#intValue}.
*/
public final static class IntLikeSerializer
- extends SerializerBase<Number>
+ extends ScalarSerializerBase<Number>
{
final static IntLikeSerializer instance = new IntLikeSerializer();
@@ -145,7 +187,7 @@
}
public final static class LongSerializer
- extends SerializerBase<Long>
+ extends ScalarSerializerBase<Long>
{
final static LongSerializer instance = new LongSerializer();
@@ -166,7 +208,7 @@
}
public final static class FloatSerializer
- extends SerializerBase<Float>
+ extends ScalarSerializerBase<Float>
{
final static FloatSerializer instance = new FloatSerializer();
@@ -185,9 +227,16 @@
return createSchemaNode("number", true);
}
}
-
+
+ /**
+ * This is the special serializer for regular {@link java.lang.Double}s
+ * (and primitive doubles)
+ *<p>
+ * Since this is one of "native" types, no type information is ever
+ * included on serialization (unlike for most scalar types as of 1.5)
+ */
public final static class DoubleSerializer
- extends SerializerBase<Double>
+ extends NonTypedScalarSerializer<Double>
{
final static DoubleSerializer instance = new DoubleSerializer();
@@ -212,7 +261,7 @@
* types of {@link Number}s (custom types).
*/
public final static class NumberSerializer
- extends SerializerBase<Number>
+ extends ScalarSerializerBase<Number>
{
public final static NumberSerializer instance = new NumberSerializer();
@@ -254,7 +303,7 @@
* and json.
*/
public final static class CalendarSerializer
- extends SerializerBase<Calendar>
+ extends ScalarSerializerBase<Calendar>
{
public final static CalendarSerializer instance = new CalendarSerializer();
@@ -281,7 +330,7 @@
* potentially more readable Strings.
*/
public final static class UtilDateSerializer
- extends SerializerBase<java.util.Date>
+ extends ScalarSerializerBase<java.util.Date>
{
public final static UtilDateSerializer instance = new UtilDateSerializer();
@@ -310,7 +359,7 @@
* that should not be used by plain SQL date.
*/
public final static class SqlDateSerializer
- extends SerializerBase<java.sql.Date>
+ extends ScalarSerializerBase<java.sql.Date>
{
public SqlDateSerializer() { super(java.sql.Date.class); }
@@ -330,7 +379,7 @@
}
public final static class SqlTimeSerializer
- extends SerializerBase<java.sql.Time>
+ extends ScalarSerializerBase<java.sql.Time>
{
public SqlTimeSerializer() { super(java.sql.Time.class); }
@@ -361,6 +410,7 @@
* Note: given that this is used for anything that implements
* interface, can not be checked for direct class equivalence.
*/
+ @SuppressWarnings("deprecation")
public final static class SerializableSerializer
extends SerializerBase<JsonSerializable>
{
@@ -374,6 +424,22 @@
{
value.serialize(jgen, provider);
}
+
+ @Override
+ public final void serializeWithType(JsonSerializable value, JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonGenerationException
+ {
+ /* 24-Jan-2009, tatus: This is not quite optimal (perhaps we should
+ * just create separate serializer...), but works until 2.0 will
+ * deprecate non-typed interface
+ */
+ if (value instanceof JsonSerializableWithType) {
+ ((JsonSerializableWithType) value).serializeWithType(jgen, provider, typeSer);
+ } else {
+ this.serialize(value, jgen, provider);
+ }
+ }
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
@@ -436,6 +502,22 @@
value.serialize(jgen);
}
+ /**
+ * Implementing typed output for contents of a TokenBuffer is very tricky,
+ * since we do not know for sure what its contents might look like.
+ * One possibility would be to check the current token, and use that to
+ * determine if we would output Json Array, Object or scalar value.
+ * That might or might now work,
+ * so for now (as of 1.5), let's not output any type information.
+ */
+ @Override
+ public final void serializeWithType(TokenBuffer value, JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonGenerationException
+ {
+ serialize(value, jgen, provider);
+ }
+
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
diff --git a/src/mapper/java/org/codehaus/jackson/node/BaseJsonNode.java b/src/mapper/java/org/codehaus/jackson/node/BaseJsonNode.java
index 8f79fbe..d36ad7e 100644
--- a/src/mapper/java/org/codehaus/jackson/node/BaseJsonNode.java
+++ b/src/mapper/java/org/codehaus/jackson/node/BaseJsonNode.java
@@ -3,8 +3,9 @@
import java.io.IOException;
import org.codehaus.jackson.*;
-import org.codehaus.jackson.map.JsonSerializable;
+import org.codehaus.jackson.map.JsonSerializableWithType;
import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.TypeSerializer;
/**
* Abstract base class common to all standard {@link JsonNode}
@@ -15,7 +16,7 @@
*/
public abstract class BaseJsonNode
extends JsonNode
- implements JsonSerializable
+ implements JsonSerializableWithType
{
protected BaseJsonNode() { }
@@ -61,6 +62,17 @@
public abstract void serialize(JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException;
+ /**
+ * Since JSON node typing is only based on JSON values,
+ * there is no need to include type information. So, serialize
+ * the same way as when no typing is enabled.
+ */
+ public void serializeWithType(JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonProcessingException
+ {
+ serialize(jgen, provider);
+ }
/*
*********************************************
diff --git a/src/test/org/codehaus/jackson/map/TestObjectMapperBeanDeserializer.java b/src/test/org/codehaus/jackson/map/TestObjectMapperBeanDeserializer.java
index 90d92bd..e6cd097 100644
--- a/src/test/org/codehaus/jackson/map/TestObjectMapperBeanDeserializer.java
+++ b/src/test/org/codehaus/jackson/map/TestObjectMapperBeanDeserializer.java
@@ -20,6 +20,7 @@
/////////////////////////////////////////////////
*/
+ @SuppressWarnings("deprecation")
final static class CtorValueBean
implements JsonSerializable // so we can output as simple String
{
diff --git a/src/test/org/codehaus/jackson/map/deser/TestArrayDeserialization.java b/src/test/org/codehaus/jackson/map/deser/TestArrayDeserialization.java
index 18038a4..2500a57 100644
--- a/src/test/org/codehaus/jackson/map/deser/TestArrayDeserialization.java
+++ b/src/test/org/codehaus/jackson/map/deser/TestArrayDeserialization.java
@@ -415,6 +415,7 @@
* Deserialization from String value will be done via single-arg
* constructor.
*/
+ @SuppressWarnings("deprecation")
public final static class Bean2
implements JsonSerializable // so we can output as simple String
{
diff --git a/src/test/org/codehaus/jackson/map/jsontype/TestDefaultForObject.java b/src/test/org/codehaus/jackson/map/jsontype/TestDefaultForObject.java
index d4bf324..e5da3a6 100644
--- a/src/test/org/codehaus/jackson/map/jsontype/TestDefaultForObject.java
+++ b/src/test/org/codehaus/jackson/map/jsontype/TestDefaultForObject.java
@@ -13,7 +13,9 @@
******************************************************
*/
- static class StringBean { // ha, punny!
+ static abstract class AbstractBean { }
+
+ static class StringBean extends AbstractBean { // ha, punny!
public String name;
public StringBean() { this(null); }
@@ -25,7 +27,12 @@
* Unit tests
******************************************************
*/
-
+
+ /**
+ * Unit test that verifies that a bean is stored with type information,
+ * when declared type is <code>Object.class</code> (since it is within
+ * Object[]), and default type information is enabled.
+ */
public void testBeanAsObject() throws Exception
{
ObjectMapper m = new ObjectMapper();
@@ -33,11 +40,9 @@
// note: need to wrap, to get declared as Object
String str = m.writeValueAsString(new Object[] { new StringBean("abc") });
- verifySerializationAsMap(str);
+ _verifySerializationAsMap(str);
// Ok: serialization seems to work as expected. Now deserialize:
- //System.err.println("DEBUG: json = "+str);
-
Object ob = m.readValue(str, Object[].class);
assertNotNull(ob);
Object[] result = (Object[]) ob;
@@ -47,7 +52,7 @@
}
@SuppressWarnings("unchecked")
- private void verifySerializationAsMap(String str) throws Exception
+ private void _verifySerializationAsMap(String str) throws Exception
{
// First: validate that structure looks correct (as Map etc)
// note: should look something like:
@@ -68,4 +73,33 @@
assertEquals(1, map.size());
assertEquals("abc", map.get("name"));
}
+
+ /**
+ * Unit test that verifies that an abstract bean is stored with type information
+ * if default type information is enabled for non-concrete types.
+ */
+ public void testAbstractBean() throws Exception
+ {
+ // First, let's verify that we'd fail without enabling default type info
+ ObjectMapper m = new ObjectMapper();
+ AbstractBean[] input = new AbstractBean[] { new StringBean("xyz") };
+ String serial = m.writeValueAsString(input);
+ try {
+ m.readValue(serial, AbstractBean[].class);
+ fail("Should have failed");
+ } catch (JsonMappingException e) {
+ // let's use whatever is currently thrown exception... may change tho
+ verifyException(e, "can not instantiate from JSON object");
+ }
+
+ // and then that we will succeed with default type info
+ m = new ObjectMapper();
+ m.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
+ serial = m.writeValueAsString(input);
+ AbstractBean[] beans = m.readValue(serial, AbstractBean[].class);
+ assertEquals(1, beans.length);
+ assertEquals(StringBean.class, beans[0].getClass());
+ assertEquals("xyz", ((StringBean) beans[0]).name);
+ }
+
}
diff --git a/src/test/org/codehaus/jackson/map/jsontype/TestDefaultForScalars.java b/src/test/org/codehaus/jackson/map/jsontype/TestDefaultForScalars.java
new file mode 100644
index 0000000..4277de7
--- /dev/null
+++ b/src/test/org/codehaus/jackson/map/jsontype/TestDefaultForScalars.java
@@ -0,0 +1,65 @@
+package org.codehaus.jackson.map.jsontype;
+
+import java.util.*;
+
+import org.codehaus.jackson.map.*;
+
+/**
+ * Unit tests to verify that Java/JSON scalar values (non-structured values)
+ * are handled properly with respect to additional type information.
+ *
+ * @since 1.5
+ * @author tatu
+ */
+public class TestDefaultForScalars
+ extends BaseMapTest
+{
+ /**
+ * Unit test to verify that limited number of core types do NOT include
+ * type information, even if declared as Object. This is only done for types
+ * that JSON scalar values natively map to: String, Integer and Boolean (and
+ * nulls never have type information)
+ */
+ public void testNumericScalars() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ m.enableDefaultTyping();
+
+ // no typing for Integer, Double, yes for others
+ assertEquals("[123]", m.writeValueAsString(new Object[] { Integer.valueOf(123) }));
+ assertEquals("[[\"java.lang.Long\",37]]", m.writeValueAsString(new Object[] { Long.valueOf(37) }));
+ assertEquals("[0.25]", m.writeValueAsString(new Object[] { Double.valueOf(0.25) }));
+ assertEquals("[[\"java.lang.Float\",0.5]]", m.writeValueAsString(new Object[] { Float.valueOf(0.5f) }));
+ }
+
+ public void testDateScalars() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ m.enableDefaultTyping();
+
+ long ts = 12345678L;
+ assertEquals("[[\"java.util.Date\","+ts+"]]",
+ m.writeValueAsString(new Object[] { new Date(ts) }));
+
+ // Calendar is trickier... hmmh. Need to ensure round-tripping
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(ts);
+ String json = m.writeValueAsString(new Object[] { c });
+ assertEquals("[[\""+c.getClass().getName()+"\","+ts+"]]", json);
+ // and let's make sure it also comes back same way:
+ Object[] result = m.readValue(json, Object[].class);
+ assertEquals(1, result.length);
+ assertTrue(result[0] instanceof Calendar);
+ assertEquals(ts, ((Calendar) result[0]).getTimeInMillis());
+ }
+
+ public void testMiscScalars() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ m.enableDefaultTyping();
+
+ // no typing for Strings, booleans
+ assertEquals("[\"abc\"]", m.writeValueAsString(new Object[] { "abc" }));
+ assertEquals("[true,null,false]", m.writeValueAsString(new Boolean[] { true, null, false }));
+ }
+}
diff --git a/src/test/org/codehaus/jackson/schema/TestReadJsonSchema.java b/src/test/org/codehaus/jackson/schema/TestReadJsonSchema.java
index 162989c..c3ea3a9 100644
--- a/src/test/org/codehaus/jackson/schema/TestReadJsonSchema.java
+++ b/src/test/org/codehaus/jackson/schema/TestReadJsonSchema.java
@@ -27,7 +27,7 @@
public double[] doubles;
public Object[] objects;
- public JsonSerializable someSerializable;
+ public JsonSerializableWithType someSerializable;
public Iterable<Object> iterableOhYeahBaby;