Skip to content

Commit

Permalink
Merge pull request #133 from owlike/skip_class_metadata_jsonp
Browse files Browse the repository at this point in the history
Better control for class metadata ser/de and JsonP extension
  • Loading branch information
EugenCepoi authored Jun 6, 2018
2 parents 5689fcc + f44c8e6 commit f1ed7ec
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,36 +54,53 @@ 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;
}
}

private final boolean classMetadataWithStaticType;
private final Class<T> tClass;
private final boolean skipMetadataSerialization;
private final boolean writeClassMetadata;
private final boolean readClassMetadata;

public ClassMetadataConverter(Class<T> tClass, Converter<T> delegate, boolean classMetadataWithStaticType) {
public ClassMetadataConverter(Class<T> tClass, Converter<T> 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()));
}
wrapped.serialize(obj, writer, ctx);
}

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,7 @@ public final static class UntypedConverterFactory implements Factory<Converter<O
private UntypedConverterFactory() {
}

@HandleClassMetadata(serialization = true, deserialization = false)
public final static class UntypedConverter implements Converter<Object> {
final static UntypedConverter instance = new UntypedConverter();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,8 +29,21 @@ public class JSR353Bundle extends GensonBundle {
static final JsonBuilderFactory factory = JsonProvider.provider().createBuilderFactory(
new HashMap<String, String>());

private boolean readUnknownTypesAsJsonValue = false;

@Override
public void configure(GensonBuilder builder) {
if (readUnknownTypesAsJsonValue) {
builder.withDeserializerFactory(new Factory<Converter<?>>() {
@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<Converter<JsonValue>>() {
@Override
public Converter<JsonValue> create(Type type, Genson genson) {
Expand All @@ -37,6 +52,19 @@ public Converter<JsonValue> 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<JsonValue> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit f1ed7ec

Please sign in to comment.