How to serialize a class with an interface?

前端 未结 4 1009
南方客
南方客 2020-11-29 23:18

I have never done much with serialization, but am trying to use Google\'s gson to serialize a Java object to a file. Here is an example of my issue:

public i         


        
相关标签:
4条回答
  • @Maciek solution works perfect if the declared type of the member variable is the interface / abstract class. It won't work if the declared type is sub-class / sub-interface / sub-abstract class unless we register them all through registerTypeAdapter(). We can avoid registering one by one with the use of registerTypeHierarchyAdapter, but I realize that it will cause StackOverflowError because of the infinite loop. (Please read reference section below)

    In short, my workaround solution looks a bit senseless but it works without StackOverflowError.

    @Override
    public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
        final JsonObject wrapper = new JsonObject();
        wrapper.addProperty("type", object.getClass().getName());
        wrapper.add("data", new Gson().toJsonTree(object));
        return wrapper;
    }
    

    I used another new Gson instance of work as the default serializer / deserializer to avoid infinite loop. The drawback of this solution is you will also lose other TypeAdapter as well, if you have custom serialization for another type and it appears in the object, it will simply fail.

    Still, I am hoping for a better solution.

    Reference

    According to Gson 2.3.1 documentation for JsonSerializationContext and JsonDeserializationContext

    Invokes default serialization on the specified object passing the specific type information. It should never be invoked on the element received as a parameter of the JsonSerializer.serialize(Object, Type, JsonSerializationContext) method. Doing so will result in an infinite loop since Gson will in-turn call the custom serializer again.

    and

    Invokes default deserialization on the specified object. It should never be invoked on the element received as a parameter of the JsonDeserializer.deserialize(JsonElement, Type, JsonDeserializationContext) method. Doing so will result in an infinite loop since Gson will in-turn call the custom deserializer again.

    This concludes that below implementation will cause infinite loop and cause StackOverflowError eventually.

    @Override
    public JsonElement serialize(Animal src, Type typeOfSrc,
            JsonSerializationContext context) {
        return context.serialize(src);
    }
    
    0 讨论(0)
  • 2020-11-29 23:34

    Here is a generic solution that works for all cases where only interface is known statically.

    1. Create serialiser/deserialiser:

      final class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {
          public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
              final JsonObject wrapper = new JsonObject();
              wrapper.addProperty("type", object.getClass().getName());
              wrapper.add("data", context.serialize(object));
              return wrapper;
          }
      
          public T deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
              final JsonObject wrapper = (JsonObject) elem;
              final JsonElement typeName = get(wrapper, "type");
              final JsonElement data = get(wrapper, "data");
              final Type actualType = typeForName(typeName); 
              return context.deserialize(data, actualType);
          }
      
          private Type typeForName(final JsonElement typeElem) {
              try {
                  return Class.forName(typeElem.getAsString());
              } catch (ClassNotFoundException e) {
                  throw new JsonParseException(e);
              }
          }
      
          private JsonElement get(final JsonObject wrapper, String memberName) {
              final JsonElement elem = wrapper.get(memberName);
              if (elem == null) throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");
              return elem;
          }
      }
      
    2. make Gson use it for the interface type of your choice:

      Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter<Animal>())
                                   .create();
      
    0 讨论(0)
  • 2020-11-29 23:40

    I had the same problem, except my interface was of primitive type (CharSequence) and not JsonObject:

    if (elem instanceof JsonPrimitive){
        JsonPrimitive primitiveObject = (JsonPrimitive) elem;
    
        Type primitiveType = 
        primitiveObject.isBoolean() ?Boolean.class : 
        primitiveObject.isNumber() ? Number.class :
        primitiveObject.isString() ? String.class :
        String.class;
    
        return context.deserialize(primitiveObject, primitiveType);
    }
    
    if (elem instanceof JsonObject){
        JsonObject wrapper = (JsonObject) elem;         
    
        final JsonElement typeName = get(wrapper, "type");
        final JsonElement data = get(wrapper, "data");
        final Type actualType = typeForName(typeName); 
        return context.deserialize(data, actualType);
    }
    
    0 讨论(0)
  • 2020-11-29 23:52

    Put the animal as transient, it will then not be serialized.

    Or you can serialize it yourself by implementing defaultWriteObject(...) and defaultReadObject(...) (I think thats what they were called...)

    EDIT See the part about "Writing an Instance Creator" here.

    Gson cant deserialize an interface since it doesnt know which implementing class will be used, so you need to provide an instance creator for your Animal and set a default or similar.

    0 讨论(0)
提交回复
热议问题