Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better control for class metadata ser/de and JsonP extension #133

Merged
merged 1 commit into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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