简体   繁体   中英

Adding a custom TypeAdapterFactory for Google GSON to detect use of annotations to return custom TypeAdapter dynamically

Let us start with sharing working and pastable code (requires google gson package):

package mypackage;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

public final class ALL {
    static final Gson GSON = new GsonBuilder().registerTypeAdapterFactory(new Factory()).create();

    /////////////////////////////////////////////////////////////////////

    @Target( { METHOD, FIELD, ANNOTATION_TYPE, TYPE })
    @Retention(RUNTIME)
    public @interface Serialize {}

    /////////////////////////////////////////////////////////////////////

    public static void main(String[] args) {
        Test test = new Test();

        String json = GSON.toJson(test);

        System.out.println(json);
    }

    /////////////////////////////////////////////////////////////////////

    public static final class Test {
        @Serialize
        String abc = "def";
    }

    /////////////////////////////////////////////////////////////////////

    public static final class Factory implements TypeAdapterFactory {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            Serialize annotation = type.getRawType().getAnnotation(Serialize.class);

            boolean annotationPresent = type.getRawType().isAnnotationPresent(Serialize.class);

            Annotation[] annotations = type.getRawType().getAnnotations();

            if (annotationPresent) {
                System.out.println("11111111111111");
            }

            if (annotation != null) {
                return new Adapter<>();
            }

            return gson.getDelegateAdapter(this, type);
        }
    }

    /////////////////////////////////////////////////////////////////////

    public static final class Adapter<T> extends TypeAdapter<T> {
        private static final java.util.Base64.Encoder ENCODER = java.util.Base64.getEncoder();
        private static final java.util.Base64.Decoder DECODER = java.util.Base64.getDecoder();

        @Override
        public T read(JsonReader in) throws IOException {
            in.beginObject();
            String a = in.nextString();
            in.endObject();

            try {
                return deserialize( DECODER.decode(a) );
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void write(JsonWriter out, T value) throws IOException {
            out.value( encode(serialize(value)) );
        }

        private String encode(byte[] serialize) {
            return ENCODER.encodeToString( serialize );
        }

        private byte[] serialize(T value) throws IOException {
            try (ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream os = new ObjectOutputStream(out); ) {
                os.writeObject(value);
                return out.toByteArray();
            }
        }

        private T deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
            try (ByteArrayInputStream in = new ByteArrayInputStream(bytes); ObjectInputStream is = new ObjectInputStream(in); ) {
                return (T) is.readObject();
            }
        }
    }
}

If we look at Test class, the goal is to output something else if the annotation @Serialize is present. In this case we output bytes in String. And when we then read this back, we would like to deserialize it.

Other ways of understanding the goal is to think of maybe using an annotation you would like to encrypt a value and you could decrypt it on readback.

This should be possible, no?

I know i can register TypeAdapters based on field type, however, I would like to be able to use annotations to declare intent instead.

No wrapper classes. You can create a custom JsonSerializer but this requires registering.

In the example above, the type.getRawType().getAnnotation(Serialize.class); is always returning null and Annotation[] annotations = type.getRawType().getAnnotations() always empty, so unable to detect using the factory.

Unsure how to detect the annotation dynamically.

Do you know?

How about using @JsonAdapter? You anyway need to know how to do de-/crypting and need to implement tha per type. For string in your case, for example:

public class CryptoAdapter extends TypeAdapter<String> {
    @Override
    public void write(JsonWriter out, String value) throws IOException {
        out.jsonValue(org.apache.commons.lang3.StringUtils.reverse(value));
    }
    @Override
    public String read(JsonReader in) throws IOException {
        return org.apache.commons.lang3.StringUtils.reverse(in.nextString());
    }
}

Usage:

public class Test {
    @JsonAdapter(CryptoAdapter.class)
    String abc = "def";
}

The problem is that Gson does not provide (to my knowledge) any direct means to create some own field processor that lets user to read the field/class member annotations.

In other words you need an access to the field during de-/serialization and that seem not to be possible in an easy way.

That is why there is this @JsonAdapter .

If interested to study more clone source code from GitHub and check:

public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory

Unfortunately final . There is a method named createBoundField (which I think is the logic behind recognizing @JsonAdapter for fields ) and the path and overriding that logic is not so straightforward.

For classes there seems to be solution quite similar to yours:

public final class JsonAdapterAnnotationTypeAdapterFactory
    implements TypeAdapterFactory

Both above mentioned are added to the list of TypeAdapterFactories when a new Gson is created.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM