diff --git a/README.md b/README.md index 8f98644..8fe01ac 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Gson gson = builder.createGson(); ### Expose your methods -You can annotate methods to be automatically evaluated and serialized +You can annotate methods to be automatically evaluated and serialized: ```java @@ -92,8 +92,27 @@ GsonFireBuilder builder = new GsonFireBuilder() ``` +This works for deserialization as well: + +```java + +public void SomeClass{ + + @ExposeMethodParam("name") + public void setName(String name){ + // Obviously, this is only useful on more complicated methods + this.name = name; + } +} + +//Then +GsonFireBuilder builder = new GsonFireBuilder() + .enableExposeMethodParam(); //This will make Gson to call all methods + //annotated with @ExposeMethodParam + +``` You can use ```GsonFireBuilder.addSerializationExclusionStrategy``` if you want to add custom exclusion strategies for -some methods. +some methods. You can also specify a `ConflictResolutionStrategy` if a method exposes a value with the same name as a field. ### Date format @@ -129,6 +148,13 @@ public void SomeClass{ //this method will get invoked just before //the class is serialized to gson } + + @PostSerialize + public void postSerializeLogic() { + //this method will get invoked after the class + //has been serialized to json, before all the hooks + //are run. + } @PostDeserialize public void postDeserializeLogic(){ @@ -148,7 +174,25 @@ Gson builder = new GsonFireBuilder() Gson gson = builder.createGson(); ``` -Any `Exception` thrown inside the hooks will be wrapped into a `HookInvocationException` +Any `Exception` thrown inside the hooks will be wrapped into a `HookInvocationException`. + +The hook method can also be written as `preSerializeLogic(JsonElement src, Gson gson)`. As an example, +using this, you can serialize `byte[]` fields to Base64 strings: + +```java +@Exclude +byte[] data; + +@PostSerialize +private void postSerialize(JsonElement src, Gson gson) { + src.getAsJsonObject().addProperty("data", Base64.getEncoder().encodeToString(data)); +} + +@PostDeserialize +private void postDeserialize(JsonElement src, Gson gson) { + data = Base64.getDecoder().decode(src.getAsJsonObject().getAsJsonPrimitive("data").getAsString()); +} +``` ### Iterable Serialization @@ -175,6 +219,14 @@ for(Integer i: simpleIterable) { ``` +### Exclude fields + +Gson has an `Expose` annotation if you only want to serialize single fields. If you want to exclude single fields, +you can make them `transient` and ignore them with `gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT)`. +This will have other implications on other Java serialization tools too. If you want to ignore specific fields, but +only in Gson, annotate them with `Exclude`, `ExcludeSerialize` and `ExcludeDeserialize`. Enable this feature +using `fireBuilder.enableExcludeByAnnotation()`. + ### Excude fields depending on its value Gson allows to define custom exclusion strategies for fields. However it is not possible to exclude a field depending diff --git a/src/main/java/io/gsonfire/AnnotationExclusionStrategy.java b/src/main/java/io/gsonfire/AnnotationExclusionStrategy.java new file mode 100644 index 0000000..4a19c75 --- /dev/null +++ b/src/main/java/io/gsonfire/AnnotationExclusionStrategy.java @@ -0,0 +1,35 @@ +package io.gsonfire; + +import java.lang.annotation.Annotation; +import java.util.Objects; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +/** + * Exclude all fields annotated with a specific annotation. + * + * Forked from https://stackoverflow.com/a/27986860/6094756. + * + * @param T + * the type of the annotation class + * @author piegames + */ +public class AnnotationExclusionStrategy implements ExclusionStrategy { + + private Class clazz; + + public AnnotationExclusionStrategy(Class clazz) { + this.clazz = Objects.requireNonNull(clazz); + } + + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(clazz) != null; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } +} diff --git a/src/main/java/io/gsonfire/GsonFireBuilder.java b/src/main/java/io/gsonfire/GsonFireBuilder.java index 2d96be6..571e0e4 100644 --- a/src/main/java/io/gsonfire/GsonFireBuilder.java +++ b/src/main/java/io/gsonfire/GsonFireBuilder.java @@ -1,9 +1,30 @@ package io.gsonfire; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import io.gsonfire.gson.*; + +import io.gsonfire.annotations.Exclude; +import io.gsonfire.annotations.ExcludeDeserialize; +import io.gsonfire.annotations.ExcludeSerialize; +import io.gsonfire.gson.EnumDefaultValueTypeAdapterFactory; +import io.gsonfire.gson.ExcludeByValueTypeAdapterFactory; +import io.gsonfire.gson.FireExclusionStrategy; +import io.gsonfire.gson.FireExclusionStrategyComposite; +import io.gsonfire.gson.HooksTypeAdapterFactory; +import io.gsonfire.gson.SimpleIterableTypeAdapterFactory; +import io.gsonfire.gson.TypeSelectorTypeAdapterFactory; +import io.gsonfire.gson.WrapTypeAdapterFactory; import io.gsonfire.postprocessors.MergeMapPostProcessor; import io.gsonfire.postprocessors.methodinvoker.MethodInvokerPostProcessor; import io.gsonfire.util.Mapper; @@ -11,9 +32,6 @@ import io.gsonfire.util.reflection.Factory; import io.gsonfire.util.reflection.FieldInspector; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - /** * @autor: julio */ @@ -31,6 +49,8 @@ public final class GsonFireBuilder { private boolean dateDeserializationStrict = true; private TimeZone serializeTimeZone = TimeZone.getDefault(); private boolean enableExposeMethodResults = false; + private boolean enableExposeMethodParams = false; + private boolean enableExcludeByAnnotation = false; private boolean enableExclusionByValueStrategies = false; private ClassConfig getClassConfig(Class clazz){ @@ -156,6 +176,37 @@ public GsonFireBuilder enableExposeMethodResult(){ return this; } + /** + * By enabling this, all methods with the annotation {@link io.gsonfire.annotations.ExposeMethodParam} will be called with appropriate data + * parsed from the json tree. + * + * @return + */ + public GsonFireBuilder enableExposeMethodParam() { + this.enableExposeMethodParams = true; + return this; + } + + /** + * By enabling this, all fields with the annotation {@link io.gsonfire.annotations.Exclude}, + * {@link io.gsonfire.annotations.ExcludeSerialize} and {@link io.gsonfire.annotations.ExcludeDeserialize} will be evaluated and it result + * will be excluded from serialization, deserialization and/or both. + *

+ * This is equivalent to calling + * + *

+	 * builder.setExclusionStrategies(new AnnotationExclusionStrategy(Exclude.class));
+	 * builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy(ExcludeSerialize.class));
+	 * builder.addDeserializationExclusionStrategy(new AnnotationExclusionStrategy(ExcludeDeserialize.class));
+	 * 
+ * + * @return + */ + public GsonFireBuilder enableExcludeByAnnotation(){ + this.enableExcludeByAnnotation = true; + return this; + } + /** * By enabling this, all exclusion by value strategies specified with the annotation * {@link io.gsonfire.annotations.ExcludeByValue} will be run to remove specific fields from the resulting json @@ -228,15 +279,22 @@ public GsonBuilder createGsonBuilder(){ Set alreadyResolvedTypeTokensRegistry = Collections.newSetFromMap(new ConcurrentHashMap()); GsonBuilder builder = new GsonBuilder(); - if(enableExposeMethodResults) { - FireExclusionStrategy compositeExclusionStrategy = new FireExclusionStrategyComposite(serializationExclusions); - registerPostProcessor(Object.class, new MethodInvokerPostProcessor(compositeExclusionStrategy)); + if (enableExposeMethodParams || enableExposeMethodResults) { + FireExclusionStrategy compositeExclusionStrategy = new FireExclusionStrategyComposite(serializationExclusions); + registerPostProcessor(Object.class, new MethodInvokerPostProcessor(compositeExclusionStrategy, enableExposeMethodParams, + enableExposeMethodResults)); } if(enableExclusionByValueStrategies) { builder.registerTypeAdapterFactory(new ExcludeByValueTypeAdapterFactory(fieldInspector, factory)); } + if (enableExcludeByAnnotation) { + builder.setExclusionStrategies(new AnnotationExclusionStrategy(Exclude.class)); + builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy(ExcludeSerialize.class)); + builder.addDeserializationExclusionStrategy(new AnnotationExclusionStrategy(ExcludeDeserialize.class)); + } + for(Class clazz: orderedClasses){ ClassConfig config = classConfigMap.get(clazz); if(config.getTypeSelector() != null) { diff --git a/src/main/java/io/gsonfire/annotations/Exclude.java b/src/main/java/io/gsonfire/annotations/Exclude.java new file mode 100644 index 0000000..a81a8d3 --- /dev/null +++ b/src/main/java/io/gsonfire/annotations/Exclude.java @@ -0,0 +1,22 @@ +package io.gsonfire.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.gson.annotations.Expose; + +/** + * A counter-part to GSON'S {@link Expose} annotation. It is used analogously, but with inverse functionality. Fields marked with this + * annotation will be excluded, while all others will be serialized. + * + * @see Expose + * @author piegames + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Exclude { +} diff --git a/src/main/java/io/gsonfire/annotations/ExcludeDeserialize.java b/src/main/java/io/gsonfire/annotations/ExcludeDeserialize.java new file mode 100644 index 0000000..8b20848 --- /dev/null +++ b/src/main/java/io/gsonfire/annotations/ExcludeDeserialize.java @@ -0,0 +1,22 @@ +package io.gsonfire.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.gson.annotations.Expose; + +/** + * A counter-part to GSON'S {@link Expose} annotation. It is used analogously, but with inverse functionality. Fields marked with this + * annotation will be excluded, while all others will be serialized. + * + * @see Expose + * @author piegames + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ExcludeDeserialize { +} diff --git a/src/main/java/io/gsonfire/annotations/ExcludeSerialize.java b/src/main/java/io/gsonfire/annotations/ExcludeSerialize.java new file mode 100644 index 0000000..7e4266c --- /dev/null +++ b/src/main/java/io/gsonfire/annotations/ExcludeSerialize.java @@ -0,0 +1,22 @@ +package io.gsonfire.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.gson.annotations.Expose; + +/** + * A counter-part to GSON'S {@link Expose} annotation. It is used analogously, but with inverse functionality. Fields marked with this + * annotation will be excluded, while all others will be serialized. + * + * @see Expose + * @author piegames + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ExcludeSerialize { +} diff --git a/src/main/java/io/gsonfire/annotations/ExposeMethodParam.java b/src/main/java/io/gsonfire/annotations/ExposeMethodParam.java new file mode 100644 index 0000000..638dc1b --- /dev/null +++ b/src/main/java/io/gsonfire/annotations/ExposeMethodParam.java @@ -0,0 +1,29 @@ +package io.gsonfire.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.gsonfire.annotations.ExposeMethodResult.ConflictResolutionStrategy; + +/** + * Methods annotated with {@link ExposeMethodParam} must take exactly one argument. If enabled, their argument will be parsed from the json + * data and they will be called with that value during post-processing. Any return values will be ignored by Gson. + * + * @autor piegames + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ExposeMethodParam { + + /** + * @return The name of the field to call the method with + */ + String value(); + + /** + * @return Strategy to be used when there is conflict between the name of a field on the Java Object vs the field name + */ + ConflictResolutionStrategy conflictResolution() default ConflictResolutionStrategy.OVERWRITE; +} diff --git a/src/main/java/io/gsonfire/annotations/PostSerialize.java b/src/main/java/io/gsonfire/annotations/PostSerialize.java new file mode 100644 index 0000000..4e498e7 --- /dev/null +++ b/src/main/java/io/gsonfire/annotations/PostSerialize.java @@ -0,0 +1,14 @@ +package io.gsonfire.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @autor piegames + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PostSerialize { +} diff --git a/src/main/java/io/gsonfire/gson/HooksInvoker.java b/src/main/java/io/gsonfire/gson/HooksInvoker.java index 5dd853b..4748c0e 100644 --- a/src/main/java/io/gsonfire/gson/HooksInvoker.java +++ b/src/main/java/io/gsonfire/gson/HooksInvoker.java @@ -1,12 +1,5 @@ package io.gsonfire.gson; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import io.gsonfire.annotations.PostDeserialize; -import io.gsonfire.annotations.PreSerialize; -import io.gsonfire.util.reflection.AbstractMethodInspector; -import io.gsonfire.util.reflection.MethodInvoker; - import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -14,6 +7,15 @@ import java.util.HashSet; import java.util.Set; +import com.google.gson.Gson; +import com.google.gson.JsonElement; + +import io.gsonfire.annotations.PostDeserialize; +import io.gsonfire.annotations.PostSerialize; +import io.gsonfire.annotations.PreSerialize; +import io.gsonfire.util.reflection.AbstractMethodInspector; +import io.gsonfire.util.reflection.MethodInvoker; + /** * @autor: julio */ @@ -39,6 +41,10 @@ public void preSerialize(Object obj){ invokeAll(obj, PreSerialize.class, null, null); } + public void postSerialize(Object obj, JsonElement jsonElement, Gson gson) { + invokeAll(obj, PostSerialize.class, jsonElement, gson); + } + public void postDeserialize(Object obj, JsonElement jsonElement, Gson gson){ invokeAll(obj, PostDeserialize.class, jsonElement, gson); } diff --git a/src/main/java/io/gsonfire/gson/HooksTypeAdapter.java b/src/main/java/io/gsonfire/gson/HooksTypeAdapter.java index cfad70b..240d5a2 100644 --- a/src/main/java/io/gsonfire/gson/HooksTypeAdapter.java +++ b/src/main/java/io/gsonfire/gson/HooksTypeAdapter.java @@ -1,5 +1,7 @@ package io.gsonfire.gson; +import java.io.IOException; + import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonParser; @@ -7,13 +9,12 @@ import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; + import io.gsonfire.ClassConfig; import io.gsonfire.PostProcessor; import io.gsonfire.PreProcessor; import io.gsonfire.util.JsonUtils; -import java.io.IOException; - /** * @autor: julio */ @@ -40,6 +41,10 @@ public void write(JsonWriter out, T value) throws IOException { JsonElement res = JsonUtils.toJsonTree(originalTypeAdapter, out, value); + if (classConfig.isHooksEnabled()) { + hooksInvoker.postSerialize(value, res, gson); + } + //Run all the post serializers runPostSerialize(res, value); diff --git a/src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodParamInspector.java b/src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodParamInspector.java new file mode 100644 index 0000000..d5aa0a3 --- /dev/null +++ b/src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodParamInspector.java @@ -0,0 +1,26 @@ +package io.gsonfire.postprocessors.methodinvoker; + +import java.lang.reflect.Method; + +import io.gsonfire.annotations.ExposeMethodParam; +import io.gsonfire.util.reflection.AnnotationInspector; + +final class MappedMethodParamInspector extends AnnotationInspector { + + @Override + protected Method[] getDeclaredMembers(Class clazz) { + return clazz.getDeclaredMethods(); + } + + @Override + protected MappedMethod map(Method member) { + if (member.getParameterTypes().length != 1) { + throw new IllegalArgumentException("The methods annotated with ExposeMethodParam should have exaclty one argument"); + } + + ExposeMethodParam exposeMethodParam = member.getAnnotation(ExposeMethodParam.class); + + MappedMethod mm = new MappedMethod(member, exposeMethodParam.value(), exposeMethodParam.conflictResolution()); + return mm; + } +} diff --git a/src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodInspector.java b/src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodResultInspector.java similarity index 90% rename from src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodInspector.java rename to src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodResultInspector.java index 9c4eff2..084e215 100644 --- a/src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodInspector.java +++ b/src/main/java/io/gsonfire/postprocessors/methodinvoker/MappedMethodResultInspector.java @@ -8,7 +8,7 @@ /** * Created by julio on 7/25/15. */ -final class MappedMethodInspector extends AnnotationInspector { +final class MappedMethodResultInspector extends AnnotationInspector { @Override protected Method[] getDeclaredMembers(Class clazz) { diff --git a/src/main/java/io/gsonfire/postprocessors/methodinvoker/MethodInvokerPostProcessor.java b/src/main/java/io/gsonfire/postprocessors/methodinvoker/MethodInvokerPostProcessor.java index 4bc2791..29f75a6 100644 --- a/src/main/java/io/gsonfire/postprocessors/methodinvoker/MethodInvokerPostProcessor.java +++ b/src/main/java/io/gsonfire/postprocessors/methodinvoker/MethodInvokerPostProcessor.java @@ -1,42 +1,80 @@ package io.gsonfire.postprocessors.methodinvoker; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; + import io.gsonfire.PostProcessor; +import io.gsonfire.annotations.ExposeMethodParam; import io.gsonfire.annotations.ExposeMethodResult; import io.gsonfire.gson.FireExclusionStrategy; import io.gsonfire.gson.FireExclusionStrategyComposite; - -import java.lang.reflect.InvocationTargetException; +import io.gsonfire.util.reflection.AnnotationInspector; /** * @autor: julio */ public final class MethodInvokerPostProcessor implements PostProcessor { - private static MappedMethodInspector methodInspector = new MappedMethodInspector(); + private static AnnotationInspector methodResultInspector = new MappedMethodResultInspector(); + private static AnnotationInspector methodParamInspector = new MappedMethodParamInspector(); - private final FireExclusionStrategy serializationExclusionStrategy; + private final FireExclusionStrategy serializationExclusionStrategy; + private final boolean enableMethodParam, enableMethodResult; public MethodInvokerPostProcessor() { - this(new FireExclusionStrategyComposite()); + this(false, true); } public MethodInvokerPostProcessor(FireExclusionStrategy serializationExclusionStrategy) { + this(serializationExclusionStrategy, false, true); + } + + public MethodInvokerPostProcessor(boolean enableMethodParam, boolean enableMethodResult) { + this(new FireExclusionStrategyComposite(), enableMethodParam, enableMethodResult); + } + + public MethodInvokerPostProcessor(FireExclusionStrategy serializationExclusionStrategy, boolean enableMethodParam, boolean enableMethodResult) { this.serializationExclusionStrategy = serializationExclusionStrategy; + this.enableMethodParam = enableMethodParam; + this.enableMethodResult = enableMethodResult; } + /** @author piegames */ @Override public void postDeserialize(T result, JsonElement src, Gson gson) { - //nothing here + if (enableMethodParam && src.isJsonObject()) { + JsonObject jsonObject = src.getAsJsonObject(); + for (MappedMethod m : methodParamInspector.getAnnotatedMembers(result.getClass(), ExposeMethodParam.class)) { + if (!serializationExclusionStrategy.shouldSkipMethod(m)) { + try { + if (m.getConflictResolutionStrategy() == ExposeMethodResult.ConflictResolutionStrategy.OVERWRITE + || (m.getConflictResolutionStrategy() == ExposeMethodResult.ConflictResolutionStrategy.SKIP + && !jsonObject.has(m.getSerializedName()))) { + Method method = m.getMethod(); + Parameter param = method.getParameters()[0]; + Object value = gson.fromJson(jsonObject.get(m.getSerializedName()), param.getParameterizedType()); + method.invoke(result, value); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } + } } @Override public void postSerialize(JsonElement result, T src, Gson gson) { - if(result.isJsonObject()){ + if (enableMethodResult && result.isJsonObject()) { JsonObject jsonObject = result.getAsJsonObject(); - for(MappedMethod m: methodInspector.getAnnotatedMembers(src.getClass(), ExposeMethodResult.class)){ + for (MappedMethod m : methodResultInspector.getAnnotatedMembers(src.getClass(), ExposeMethodResult.class)) { if(!serializationExclusionStrategy.shouldSkipMethod(m)) { try { if (m.getConflictResolutionStrategy() == ExposeMethodResult.ConflictResolutionStrategy.OVERWRITE || (m.getConflictResolutionStrategy() == ExposeMethodResult.ConflictResolutionStrategy.SKIP && !jsonObject.has(m.getSerializedName()))) { diff --git a/src/test/java/io/gsonfire/gson/AnnotationInspectorTest.java b/src/test/java/io/gsonfire/gson/AnnotationInspectorTest.java index 1be461b..feec686 100644 --- a/src/test/java/io/gsonfire/gson/AnnotationInspectorTest.java +++ b/src/test/java/io/gsonfire/gson/AnnotationInspectorTest.java @@ -1,13 +1,15 @@ package io.gsonfire.gson; -import io.gsonfire.util.reflection.AnnotationInspector; -import io.gsonfire.util.reflection.MethodInspector; -import org.junit.Test; +import static org.junit.Assert.assertEquals; import java.lang.reflect.Method; import java.util.Collection; -import static org.junit.Assert.assertEquals; +import org.junit.Test; + +import io.gsonfire.annotations.ExposeMethodResult; +import io.gsonfire.util.reflection.AnnotationInspector; +import io.gsonfire.util.reflection.MethodInspector; /** @@ -19,7 +21,7 @@ public class AnnotationInspectorTest { public void testGetAnnotatedMethods() throws Exception { AnnotationInspector inspector = new MethodInspector(); - Collection methods = inspector.getAnnotatedMembers(A.class, Deprecated.class); + Collection methods = inspector.getAnnotatedMembers(A.class, ExposeMethodResult.class); assertEquals(1, methods.size()); assertEquals(A.class.getMethod("b"), methods.iterator().next()); @@ -32,7 +34,7 @@ public void a(){ } - @Deprecated + @ExposeMethodResult("b") public void b(){ } diff --git a/src/test/java/io/gsonfire/gson/ExposeMethodParamTest.java b/src/test/java/io/gsonfire/gson/ExposeMethodParamTest.java new file mode 100644 index 0000000..45d3fb1 --- /dev/null +++ b/src/test/java/io/gsonfire/gson/ExposeMethodParamTest.java @@ -0,0 +1,130 @@ +package io.gsonfire.gson; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.junit.Test; + +import com.google.gson.Gson; + +import io.gsonfire.GsonFireBuilder; +import io.gsonfire.annotations.Exclude; +import io.gsonfire.annotations.ExposeMethodParam; + +public class ExposeMethodParamTest { + + @Test + public void testCall() { + Gson gson = new GsonFireBuilder() + .enableExposeMethodParam() + .createGson(); + A a = gson.fromJson("{value: \"initialized\", field: \"correct\"}", A.class); + assertEquals("correct", a.value); + } + + private static class A { + String value = "unitialized"; + + @ExposeMethodParam("field") + void setField(String value) { + this.value = value; + } + } + + @Test + public void testData() { + Gson gson = new GsonFireBuilder() + .enableExposeMethodParam() + .enableExcludeByAnnotation() + .createGson(); + List data = Arrays.asList("hello", "world", "1234"); + B b1 = new B(); + b1.setData(data); + B b2 = gson.fromJson("{map:" + gson.toJson(data) + "}", B.class); + assertEquals(b1, b2); + } + + private static class B { + + @Exclude + Map map; + + @ExposeMethodParam("map") + void setData(List data) { + // TODO Compact this once Java 8 is enabled + map = data.stream().collect(Collectors.toMap(new Function() { + + @Override + public Integer apply(String t) { + return t.hashCode(); + } + }, new Function() { + + @Override + public String apply(String t) { + return t; + } + })); + } + + @Override + public String toString() { + return "A [map=" + map + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((map == null) ? 0 : map.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + B other = (B) obj; + if (map == null) { + if (other.map != null) + return false; + } else if (!map.equals(other.map)) + return false; + return true; + } + } + + @Test + public void testGeneric() { + Gson gson = new GsonFireBuilder() + .enableExposeMethodParam() + .enableExcludeByAnnotation() + .createGson(); + List list = Arrays.asList(1, 5, 4, 3, 10, 50, 20, 1, 3, 4); + String data = gson.toJson(list); + C c = gson.fromJson("{map:" + data + "}", C.class); + assertEquals(101, c.sum); + } + + private static class C { + @Exclude + int sum; + + @ExposeMethodParam("map") + public void sum(Collection data) { + sum = 0; + for (int i : data) + sum += i; + } + } +}