diff --git a/genson/src/main/java/com/owlike/genson/annotation/HandleClassMetadata.java b/genson/src/main/java/com/owlike/genson/annotation/HandleClassMetadata.java index a5e88b94..29d18b70 100644 --- a/genson/src/main/java/com/owlike/genson/annotation/HandleClassMetadata.java +++ b/genson/src/main/java/com/owlike/genson/annotation/HandleClassMetadata.java @@ -27,5 +27,13 @@ @Inherited @Documented public @interface HandleClassMetadata { + /** + * Set to false if you want to let the existing mechanism handle the class metadata for you. + */ + boolean serialization() default true; + /** + * @see #serialization() + */ + boolean deserialization() default true; } diff --git a/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java b/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java index 04e922fd..ee5f59ae 100644 --- a/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java +++ b/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java @@ -4,7 +4,6 @@ import com.owlike.genson.*; import com.owlike.genson.annotation.HandleClassMetadata; -import com.owlike.genson.convert.DefaultConverters.UntypedConverterFactory.UntypedConverter; import com.owlike.genson.reflect.TypeUtil; import com.owlike.genson.stream.ObjectReader; import com.owlike.genson.stream.ObjectWriter; @@ -55,9 +54,21 @@ protected Converter create(Type type, Genson genson, Converter nextConvert + "as ClassMetadataConverter can not be the last converter in the chain!"); Class rawClass = TypeUtil.getRawClass(type); - if (genson.isWithClassMetadata() - && !Wrapper.toAnnotatedElement(nextConverter).isAnnotationPresent(HandleClassMetadata.class)) - return new ClassMetadataConverter(rawClass, nextConverter, classMetadataWithStaticType); + + HandleClassMetadata handleClassMetadata = Wrapper.toAnnotatedElement(nextConverter) + .getAnnotation(HandleClassMetadata.class); + + boolean writeClassMetadata = handleClassMetadata == null || !handleClassMetadata.serialization(); + boolean readClassMetadata = handleClassMetadata == null || !handleClassMetadata.deserialization(); + + if (genson.isWithClassMetadata() && (writeClassMetadata || readClassMetadata)) + return new ClassMetadataConverter( + rawClass, + nextConverter, + classMetadataWithStaticType, + writeClassMetadata, + readClassMetadata + ); else return nextConverter; } @@ -65,18 +76,23 @@ protected Converter create(Type type, Genson genson, Converter nextConvert private final boolean classMetadataWithStaticType; private final Class tClass; - private final boolean skipMetadataSerialization; + private final boolean writeClassMetadata; + private final boolean readClassMetadata; - public ClassMetadataConverter(Class tClass, Converter delegate, boolean classMetadataWithStaticType) { + public ClassMetadataConverter(Class tClass, Converter delegate, + boolean classMetadataWithStaticType, + boolean writeClassMetadata, + boolean readClassMetadata) { super(delegate); this.tClass = tClass; this.classMetadataWithStaticType = classMetadataWithStaticType; - skipMetadataSerialization = Wrapper.isOfType(delegate, UntypedConverter.class); + this.writeClassMetadata = writeClassMetadata; + this.readClassMetadata = readClassMetadata; } public void serialize(T obj, ObjectWriter writer, Context ctx) throws Exception { - if (!skipMetadataSerialization && obj != null && - (classMetadataWithStaticType || (!classMetadataWithStaticType && !tClass.equals(obj.getClass())))) { + if (writeClassMetadata && obj != null && + (classMetadataWithStaticType || !tClass.equals(obj.getClass()))) { writer.beginNextObjectMetadata() .writeMetadata("class", ctx.genson.aliasFor(obj.getClass())); } @@ -84,7 +100,7 @@ public void serialize(T obj, ObjectWriter writer, Context ctx) throws Exception } public T deserialize(ObjectReader reader, Context ctx) throws Exception { - if (ValueType.OBJECT.equals(reader.getValueType())) { + if (readClassMetadata && ValueType.OBJECT.equals(reader.getValueType())) { String className = reader.nextObjectMetadata().metadata("class"); if (className != null) { try { diff --git a/genson/src/main/java/com/owlike/genson/convert/DefaultConverters.java b/genson/src/main/java/com/owlike/genson/convert/DefaultConverters.java index 4a8c9a76..6d91ef46 100644 --- a/genson/src/main/java/com/owlike/genson/convert/DefaultConverters.java +++ b/genson/src/main/java/com/owlike/genson/convert/DefaultConverters.java @@ -986,6 +986,7 @@ public final static class UntypedConverterFactory implements Factory { final static UntypedConverter instance = new UntypedConverter(); diff --git a/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java b/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java index 6b523c54..699154a3 100644 --- a/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java +++ b/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java @@ -18,7 +18,9 @@ import javax.json.spi.JsonProvider; import com.owlike.genson.*; +import com.owlike.genson.annotation.HandleClassMetadata; import com.owlike.genson.ext.GensonBundle; +import com.owlike.genson.reflect.TypeUtil; import com.owlike.genson.stream.JsonWriter; import com.owlike.genson.stream.ObjectReader; import com.owlike.genson.stream.ObjectWriter; @@ -27,8 +29,21 @@ public class JSR353Bundle extends GensonBundle { static final JsonBuilderFactory factory = JsonProvider.provider().createBuilderFactory( new HashMap()); + private boolean readUnknownTypesAsJsonValue = false; + @Override public void configure(GensonBuilder builder) { + if (readUnknownTypesAsJsonValue) { + builder.withDeserializerFactory(new Factory>() { + @Override + public Converter create(Type type, Genson genson) { + if (Object.class.equals(TypeUtil.getRawClass(type))) + return new JsonValueConverterWithClassMetadataForDeser(); + else return null; + } + }); + } + builder.withConverterFactory(new Factory>() { @Override public Converter create(Type type, Genson genson) { @@ -37,6 +52,19 @@ public Converter create(Type type, Genson genson) { }); } + /** + * False by default. When enabled Genson will deserialize everything that has Object as defined type to a JsonValue. + * So for example doing genson.deserialize("{}", Object.class), would return a JsonObject, instead of a Map. + */ + public JSR353Bundle readUnknownTypesAsJsonValue(boolean enable) { + readUnknownTypesAsJsonValue = true; + return this; + } + + @HandleClassMetadata(serialization = true, deserialization = false) + private class JsonValueConverterWithClassMetadataForDeser extends JsonValueConverter {} + + @HandleClassMetadata(serialization = true, deserialization = true) public class JsonValueConverter implements Converter { @Override diff --git a/genson/src/test/java/com/owlike/genson/ext/jsr353/JsonValueTest.java b/genson/src/test/java/com/owlike/genson/ext/jsr353/JsonValueTest.java index 039a6c37..bcaa7b01 100644 --- a/genson/src/test/java/com/owlike/genson/ext/jsr353/JsonValueTest.java +++ b/genson/src/test/java/com/owlike/genson/ext/jsr353/JsonValueTest.java @@ -3,6 +3,7 @@ import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonString; +import javax.json.JsonValue; import com.owlike.genson.GensonBuilder; import org.junit.Test; @@ -80,6 +81,56 @@ public void testRoundTripMixBeanAndJsonStructures() { assertEquals(object.getJsonString("key"), actual.str); } + @Test + public void classMetadataConverterShouldNotKickIn() { + Genson genson = new GensonBuilder() + .useClassMetadata(true) + .withBundle(new JSR353Bundle()) + .create(); + + String actual = genson.serialize(JSR353Bundle.factory.createObjectBuilder().build()); + + assertEquals("{}", actual); + // shouldn't fail + JsonObject value = genson.deserialize("{\"@class\": \"class.that.doesnt.exist.AH!\"}", JsonObject.class); + assertTrue(JsonObject.class.isAssignableFrom(value.getClass())); + } + + @Test + public void classMetadataConverterShouldKickIn() { + Genson genson = new GensonBuilder() + .useClassMetadata(true) + .addAlias("bean", Bean.class) + .withBundle(new JSR353Bundle()) + .create(); + + Object v = genson.deserialize("{\"@class\": \"bean\"}", Object.class); + assertTrue(Bean.class.isAssignableFrom(v.getClass())); + } + + @Test public void readUnknownTypesAsJsonValueWhenMissingClassMetadata() { + Genson genson = new GensonBuilder() + .useClassMetadata(true) + .withBundle(new JSR353Bundle().readUnknownTypesAsJsonValue(true)) + .create(); + + assertTrue(JsonObject.class.isAssignableFrom(genson.deserialize("{}", Object.class).getClass())); + } + + @Test + public void readUnknownTypesAsActualTypeWhenClassMetadataPresent() { + Genson genson = new GensonBuilder() + .useClassMetadata(true) + .addAlias("bean", Bean.class) + .withBundle(new JSR353Bundle().readUnknownTypesAsJsonValue(true)) + .create(); + + Object v = genson.deserialize("{\"@class\": \"bean\"}", Object.class); + + assertTrue(Bean.class.isAssignableFrom(v.getClass())); + } + + public static class Bean { private JsonString str; private JsonObject obj;