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

A few features #43

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
58 changes: 55 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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(){
Expand All @@ -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

Expand All @@ -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
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/io/gsonfire/AnnotationExclusionStrategy.java
Original file line number Diff line number Diff line change
@@ -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 <a href="https://stackoverflow.com/a/27986860/6094756">https://stackoverflow.com/a/27986860/6094756<a/>.
*
* @param T
* the type of the annotation class
* @author piegames
*/
public class AnnotationExclusionStrategy<T extends Annotation> implements ExclusionStrategy {

private Class<? extends T> clazz;

public AnnotationExclusionStrategy(Class<? extends T> 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;
}
}
72 changes: 65 additions & 7 deletions src/main/java/io/gsonfire/GsonFireBuilder.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
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;
import io.gsonfire.util.reflection.CachedReflectionFactory;
import io.gsonfire.util.reflection.Factory;
import io.gsonfire.util.reflection.FieldInspector;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* @autor: julio
*/
Expand All @@ -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){
Expand Down Expand Up @@ -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.
* <p/>
* This is equivalent to calling
*
* <pre>
* builder.setExclusionStrategies(new AnnotationExclusionStrategy<Exclude>(Exclude.class));
* builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy<ExcludeSerialize>(ExcludeSerialize.class));
* builder.addDeserializationExclusionStrategy(new AnnotationExclusionStrategy<ExcludeDeserialize>(ExcludeDeserialize.class));
* </pre>
*
* @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
Expand Down Expand Up @@ -228,15 +279,22 @@ public GsonBuilder createGsonBuilder(){
Set<TypeToken> alreadyResolvedTypeTokensRegistry = Collections.newSetFromMap(new ConcurrentHashMap<TypeToken, Boolean>());
GsonBuilder builder = new GsonBuilder();

if(enableExposeMethodResults) {
FireExclusionStrategy compositeExclusionStrategy = new FireExclusionStrategyComposite(serializationExclusions);
registerPostProcessor(Object.class, new MethodInvokerPostProcessor<Object>(compositeExclusionStrategy));
if (enableExposeMethodParams || enableExposeMethodResults) {
FireExclusionStrategy compositeExclusionStrategy = new FireExclusionStrategyComposite(serializationExclusions);
registerPostProcessor(Object.class, new MethodInvokerPostProcessor<Object>(compositeExclusionStrategy, enableExposeMethodParams,
enableExposeMethodResults));
}

if(enableExclusionByValueStrategies) {
builder.registerTypeAdapterFactory(new ExcludeByValueTypeAdapterFactory(fieldInspector, factory));
}

if (enableExcludeByAnnotation) {
builder.setExclusionStrategies(new AnnotationExclusionStrategy<Exclude>(Exclude.class));
builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy<ExcludeSerialize>(ExcludeSerialize.class));
builder.addDeserializationExclusionStrategy(new AnnotationExclusionStrategy<ExcludeDeserialize>(ExcludeDeserialize.class));
}

for(Class clazz: orderedClasses){
ClassConfig config = classConfigMap.get(clazz);
if(config.getTypeSelector() != null) {
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/io/gsonfire/annotations/Exclude.java
Original file line number Diff line number Diff line change
@@ -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 {
}
22 changes: 22 additions & 0 deletions src/main/java/io/gsonfire/annotations/ExcludeDeserialize.java
Original file line number Diff line number Diff line change
@@ -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 {
}
22 changes: 22 additions & 0 deletions src/main/java/io/gsonfire/annotations/ExcludeSerialize.java
Original file line number Diff line number Diff line change
@@ -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 {
}
29 changes: 29 additions & 0 deletions src/main/java/io/gsonfire/annotations/ExposeMethodParam.java
Original file line number Diff line number Diff line change
@@ -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;
}
14 changes: 14 additions & 0 deletions src/main/java/io/gsonfire/annotations/PostSerialize.java
Original file line number Diff line number Diff line change
@@ -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 {
}
20 changes: 13 additions & 7 deletions src/main/java/io/gsonfire/gson/HooksInvoker.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
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;
import java.util.Arrays;
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
*/
Expand All @@ -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);
}
Expand Down
Loading