Implemented [JACKSON-201]
diff --git a/release-notes/CREDITS b/release-notes/CREDITS
index ea0c5c7..f32a8c9 100644
--- a/release-notes/CREDITS
+++ b/release-notes/CREDITS
@@ -217,9 +217,14 @@
[1.3.1]
Mike Pilone
+ * Suggested [JACKSON-201]: Allow serialization of "empty beans" (classes
+ without getters), if SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS is
+ disabled; or if class has recognized Jackson annotation
+ [1.4.0]
* Reported [JACKSON-202]: Non-public fields not deserialized properly
with JAXB annotations
[1.3.1]
+
Stephen Friedrich
* Reported additional issues with [JACKSON-203]
diff --git a/release-notes/VERSION b/release-notes/VERSION
index ec510ba..c250a56 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -26,6 +26,10 @@
* [JACKSON-192] Added basic delegate implementations (JsonParserDelegate,
JsonGeneratorDelegate) to make it easier to override core parser and
generate behavior
+ * [JACKSON-201] Allow serialization of "empty beans" (classes without
+ getters), if SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS is
+ disabled; or if class has recognized Jackson annotation
+ (suggested by Mike P)
Other:
diff --git a/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java b/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java
index 954e8fa..637a361 100644
--- a/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java
+++ b/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java
@@ -151,6 +151,23 @@
*/
,WRAP_ROOT_VALUE(false)
+ /**
+ * Feature that determines what happens when no accessors are
+ * found for a type (and there are no annotations to indicate
+ * it is meant to be serialized). If enabled (default), an
+ * exception is thrown to indicate these as non-serializable
+ * types; if disabled, they are serialized as empty Objects,
+ * i.e. without any properties.
+ *<p>
+ * Note that empty types that this feature has only effect on
+ * those "empty" beans that do not have any recognized annotations
+ * (like <code>@JsonSerialize</code>): ones that do have annotations
+ * do not result in an exception being thrown.
+ *
+ * @since 1.4
+ */
+ ,FAIL_ON_EMPTY_BEANS(true)
+
// // // Features for datatype-specific serialization
/**
diff --git a/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java b/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java
index 944c2bd..76fbcc1 100644
--- a/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java
+++ b/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java
@@ -172,6 +172,8 @@
///////////////////////////////////////////////////////
*/
+ public boolean hasAnnotations() { return _classAnnotations.size() > 0; }
+
public AnnotatedConstructor getDefaultConstructor() { return _defaultConstructor; }
public List<AnnotatedConstructor> getConstructors()
diff --git a/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java b/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java
index b8f9f4c..2f7c055 100644
--- a/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java
+++ b/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java
@@ -49,6 +49,16 @@
public AnnotatedClass getClassInfo() { return _classInfo; }
+ public Class<?> classDescribed() { return _classInfo.getAnnotated(); }
+
+ /**
+ * Method for checking whether class being described has any
+ * annotations recognized by registered annotation introspector.
+ */
+ public boolean hasKnownClassAnnotations() {
+ return _classInfo.hasAnnotations();
+ }
+
public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes)
{
return _classInfo.findMethod(name, paramTypes);
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java
index 91ad16c..3ede7b7 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java
@@ -25,16 +25,14 @@
extends SerializerBase<Object>
implements ResolvableSerializer, SchemaAware
{
+ final static BeanPropertyWriter[] NO_PROPS = new BeanPropertyWriter[0];
+
final protected String _className;
final protected BeanPropertyWriter[] _props;
public BeanSerializer(Class<?> type, BeanPropertyWriter[] props)
{
- // sanity check
- if (props.length == 0) {
- throw new IllegalArgumentException("Can not create BeanSerializer for type that has no properties");
- }
_props = props;
// let's store this for debugging
_className = type.getName();
@@ -45,6 +43,15 @@
this(type, props.toArray(new BeanPropertyWriter[props.size()]));
}
+ /**
+ * Method for constructing dummy bean deserializer; one that
+ * never outputs any properties
+ */
+ public static BeanSerializer createDummy(Class<?> forType)
+ {
+ return new BeanSerializer(forType, NO_PROPS);
+ }
+
public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java
index 96c63c7..32e512f 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java
@@ -142,7 +142,7 @@
ser = findSerializerFromAnnotation(config, valueMethod);
return new JsonValueSerializer(valueMethod.getAnnotated(), ser);
}
- return constructBeanSerializer(type, config, beanDesc);
+ return constructBeanSerializer(config, beanDesc);
}
/*
@@ -164,20 +164,27 @@
return (ClassUtil.canBeABeanType(type) == null) && !ClassUtil.isProxyType(type);
}
- protected JsonSerializer<Object> constructBeanSerializer(Class<?> type, SerializationConfig config,
+ protected JsonSerializer<Object> constructBeanSerializer(SerializationConfig config,
BasicBeanDescription beanDesc)
{
// First: any detectable (auto-detect, annotations) properties to serialize?
List<BeanPropertyWriter> props = findBeanProperties(config, beanDesc);
if (props == null || props.size() == 0) {
// No properties, no serializer
+ /* 27-Nov-2009, tatu: Except that as per [JACKSON-201], we are
+ * ok with that as long as it has a recognized class annotation
+ * (which may come from a mix-in too)
+ */
+ if (beanDesc.hasKnownClassAnnotations()) {
+ return BeanSerializer.createDummy(beanDesc.classDescribed());
+ }
return null;
}
// Any properties to suppress?
props = filterBeanProperties(config, beanDesc, props);
// And finally: do they need to be sorted in some special way?
props = sortBeanProperties(config, beanDesc, props);
- return new BeanSerializer(type, props);
+ return new BeanSerializer(beanDesc.classDescribed(), props);
}
/**
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java b/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java
index 5f8b6cf..5cdcbe2 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java
@@ -48,12 +48,15 @@
{
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
- throws JsonMappingException
+ throws IOException, JsonMappingException
{
- /* 18-Feb-2009, tatu: Let's suggest the most likely reason
- * for failure to find a serializer
- */
- throw new JsonMappingException("No serializer found for class "+value.getClass().getName()+" (and no bean properties discovered to create bean serializer)");
+ // 27-Nov-2009, tatu: As per [JACKSON-201] may or may not fail...
+ if (provider.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS)) {
+ throw new JsonMappingException("No serializer found for class "+value.getClass().getName()+" and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )");
+ }
+ // But if it's fine, we'll just output empty JSON Object:
+ jgen.writeStartObject();
+ jgen.writeEndObject();
}
};
diff --git a/src/test/org/codehaus/jackson/map/ser/TestEmptyClass.java b/src/test/org/codehaus/jackson/map/ser/TestEmptyClass.java
new file mode 100644
index 0000000..437f1c1
--- /dev/null
+++ b/src/test/org/codehaus/jackson/map/ser/TestEmptyClass.java
@@ -0,0 +1,49 @@
+package org.codehaus.jackson.map.ser;
+
+import org.codehaus.jackson.map.*;
+import org.codehaus.jackson.map.annotate.*;
+
+public class TestEmptyClass
+ extends BaseMapTest
+{
+ static class Empty { }
+
+ @JsonSerialize
+ static class EmptyWithAnno { }
+
+ /**
+ * Test to check that [JACKSON-201] works if there is a recognized
+ * annotation (which indicates type is serializable)
+ */
+ public void testEmptyWithAnnotations() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ // First: without annotations, should complain
+ try {
+ serializeAsString(mapper, new Empty());
+ } catch (JsonMappingException e) {
+ verifyException(e, "No serializer found for class");
+ }
+
+ // But not if there is a recognized annotation
+ assertEquals("{}", serializeAsString(mapper, new EmptyWithAnno()));
+
+ // Including class annotation through mix-ins
+ mapper = new ObjectMapper();
+ mapper.getSerializationConfig().addMixInAnnotations(Empty.class, EmptyWithAnno.class);
+ assertEquals("{}", serializeAsString(mapper, new Empty()));
+ }
+
+ /**
+ * Alternative it is possible to use a feature to allow
+ * serializing empty classes, too
+ */
+ public void testEmptyWithFeature() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ // should be enabled by default
+ assertTrue(mapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS));
+ mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
+ assertEquals("{}", serializeAsString(mapper, new Empty()));
+ }
+}