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.