Gson系列2 --- 番外篇 -- 对于多态对象的解析

1、简述

> 番外篇 对于多态对象的解析  

  需要引用 使用 RuntimeTypeAdapterFactory 类解决多态问题,但是这个类需要单独下载,
  githut地址 RuntimeTypeAdapterFactory.java 
 
  1、需要将这个 RuntimeTypeAdapterFactory 类复制到项目中去,
  2、反序列化json 还需要 添加额外(或已知)的 一个唯一属性
  3、进行相应规则的注册(如下 测试) 

2、基本类

    运行时绑定类适配器工厂

package sun.rain.amazing.gson.extra;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
  private final Class<?> baseType;
  private final String typeFieldName;
  private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
  private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();

  private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
    if (typeFieldName == null || baseType == null) {
      throw new NullPointerException();
    }
    this.baseType = baseType;
    this.typeFieldName = typeFieldName;
  }

 
  public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
    return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
  }

 
  public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
    return new RuntimeTypeAdapterFactory<T>(baseType, "type");
  }

 
  public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
    if (type == null || label == null) {
      throw new NullPointerException();
    }
    if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
      throw new IllegalArgumentException("types and labels must be unique");
    }
    labelToSubtype.put(label, type);
    subtypeToLabel.put(type, label);
    return this;
  }

 
  public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
    return registerSubtype(type, type.getSimpleName());
  }

  public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
    if (type.getRawType() != baseType) {
      return null;
    }

    final Map<String, TypeAdapter<?>> labelToDelegate
        = new LinkedHashMap<String, TypeAdapter<?>>();
    final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
        = new LinkedHashMap<Class<?>, TypeAdapter<?>>();
    for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
      TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
      labelToDelegate.put(entry.getKey(), delegate);
      subtypeToDelegate.put(entry.getValue(), delegate);
    }

    return new TypeAdapter<R>() {
      @Override public R read(JsonReader in) throws IOException {
        JsonElement jsonElement = Streams.parse(in);
        JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
        if (labelJsonElement == null) {
          throw new JsonParseException("cannot deserialize " + baseType
              + " because it does not define a field named " + typeFieldName);
        }
        String label = labelJsonElement.getAsString();
        @SuppressWarnings("unchecked") // registration requires that subtype extends T
        TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
        if (delegate == null) {
          throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
              + label + "; did you forget to register a subtype?");
        }
        return delegate.fromJsonTree(jsonElement);
      }

      @Override public void write(JsonWriter out, R value) throws IOException {
        Class<?> srcType = value.getClass();
        String label = subtypeToLabel.get(srcType);
        @SuppressWarnings("unchecked") // registration requires that subtype extends T
        TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
        if (delegate == null) {
          throw new JsonParseException("cannot serialize " + srcType.getName()
              + "; did you forget to register a subtype?");
        }
        JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
        if (jsonObject.has(typeFieldName)) {
          throw new JsonParseException("cannot serialize " + srcType.getName()
              + " because it already defines a field named " + typeFieldName);
        }
        JsonObject clone = new JsonObject();
        clone.add(typeFieldName, new JsonPrimitive(label));
        for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
          clone.add(e.getKey(), e.getValue());
        }
        Streams.write(clone, out);
      }
    }.nullSafe();
  }
}

    继承类

    

/**
 * @author sunRainAmazing
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class BaseInfo {
    private String type1;
    private String ssl;
}
@Data
@NoArgsConstructor
public class GsonInfo extends BaseInfo {
    private Integer idx;
    private String gson;

    public GsonInfo(String type, String ssl, Integer idx, String gson) {
        super(type, ssl);
        this.idx = idx;
        this.gson = gson;
    }

    public GsonInfo(Integer idx, String gson) {
        this.idx = idx;
        this.gson = gson;
    }
}
@Data
@NoArgsConstructor
public class UserInfo extends BaseInfo {
    private Integer id;
    private String name;

    public UserInfo(String type, String ssl, Integer id, String name) {
        super(type, ssl);
        this.id = id;
        this.name = name;
    }

    public UserInfo(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GsonObj implements IGson {
    private BaseInfo one;
    private BaseInfo two;
}


   接口类 

   

public interface IGson {
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GsonOne implements IGson {
    private Integer idOne;
    private String uuid;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GsonTwo implements IGson {
    private Integer idTwo;
    private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
    private String name;
    private IGson iGson1;
    private IGson iGson2;
}

3、测试类

  关于继承的测试

package sun.rain.amazing.gson.ex;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.Test;
import sun.rain.amazing.gson.extra.BaseInfo;
import sun.rain.amazing.gson.extra.RuntimeTypeAdapterFactory;
import sun.rain.amazing.gson.extra.extend.GsonInfo;
import sun.rain.amazing.gson.extra.extend.UserInfo;
import sun.rain.amazing.gson.extra.extend.GsonObj;

/**
 * @author sunRainAmazing
 */
public class BaseInfoTest {

    private Gson gson = new Gson();

    /**
     * {"one":{"idx":101,"gson":"120-365","type":"A","ssl":"LINE-SSL"},
     * "two":{"id":102,"name":"tomcat","type":"B","ssl":"CODE-SSL"}}
     *
     * java.lang.RuntimeException:
     * Failed to invoke public sun.rain.amazing.gson.extra.BaseInfo() with no args
     */
    @Deprecated
    @Test
    public void testBaseInfo(){
        GsonInfo go = new GsonInfo("A","LINE-SSL",101,"120-365");
        UserInfo to = new UserInfo("B","CODE-SSL",102,"tomcat");
        GsonObj gos = new GsonObj(go,to);
        String json = gson.toJson(gos);
        System.out.println(json);
        // Failed to invoke public sun.rain.amazing.gson.extra.BaseInfo() with no args
        GsonObj g = gson.fromJson(json,GsonObj.class);
        System.out.println(g);
    }

    /**
     * 默认 会出错
  * BaseInfo because it does not define a field named type
     * 未定义类型,这里需要添加一个唯一的标识符 才可以正常反序列化 --
     * 见下一个测试
  */
    @Test
    public void testBaseInfo1(){
        GsonInfo go = new GsonInfo("A","LINE-SSL",101,"120-365");
        UserInfo to = new UserInfo("B","CODE-SSL",102,"tomcat");
        GsonObj gos = new GsonObj(go,to);
        //格式化输出
    gson = new GsonBuilder().setPrettyPrinting().create();
        String json = gson.toJson(gos);
        System.out.println(json);


        RuntimeTypeAdapterFactory<BaseInfo> typeFactory = RuntimeTypeAdapterFactory
                                //在这里,您可以指定哪个是父类,哪个字段特定于子类。
                .of(BaseInfo.class)
                                // 如果标志等于类名,则可以跳过第二个参数。 仅当类型字段不等于类名时,才需要这样做。
                .registerSubtype(GsonInfo.class)
                                .registerSubtype(UserInfo.class);

        gson = new GsonBuilder().registerTypeAdapterFactory(typeFactory).create();
        GsonObj g = gson.fromJson(json,GsonObj.class);
        System.out.println(g);
    }

    /** 因此json 必须带 一个唯一标示 且这个标识具有唯一值
   * 在 此json中 不能重复
    {
        "one": {
            "idx": 101,
            "gson": "120-365",
            "type1": "A",
            "ssl": "LINE-SSL"
    },
        "two": {
            "id": 102,
            "name": "tomcat",
            "type1": "B",
            "ssl": "CODE-SSL"
    }
    }

     将上面的格式添加 一个 uuid 唯一标识
     {
     "one": {
         "uuid": "A-one",
         "idx": 101,
         "gson": "120-365",
         "type1": "A",
         "ssl": "LINE-SSL"
     },
     "two": {
         "uuid": "A-two",
         "id": 102,
         "name": "tomcat",
         "type1": "B",
         "ssl": "CODE-SSL"
     }
     }
     此时 带有 uuid的 为 type 值  而 uuid所对应的值("A-one") 则对应子类的label
    */
    // GsonObj(one=GsonInfo(idx=101, gson=120-365), two=UserInfo(id=102, name=tomcat))
    // 才可以正常反序列化
    @Test
    public void testBaseInfoRight(){
        String json = "{\n" +
                "     \"one\": {\n" +
                "         \"uuid\": \"A-one\",\n" +
                "         \"idx\": 101,\n" +
                "         \"gson\": \"120-365\",\n" +
                "         \"type1\": \"A\",\n" +
                "         \"ssl\": \"LINE-SSL\"\n" +
                "     },\n" +
                "     \"two\": {\n" +
                "         \"uuid\": \"A-two\",\n" +
                "         \"id\": 102,\n" +
                "         \"name\": \"tomcat\",\n" +
                "         \"type1\": \"B\",\n" +
                "         \"ssl\": \"CODE-SSL\"\n" +
                "     }\n" +
                "     }";


        RuntimeTypeAdapterFactory<BaseInfo> typeFactory = RuntimeTypeAdapterFactory
                                //在这里,您可以指定哪个是父类,哪个字段特定于子类。
                // typeFieldName 的值 默认 为 type
                                .of(BaseInfo.class,"uuid")
                                // 如果标志等于类名,则可以跳过第二个参数。 仅当类型字段不等于类名时,才需要这样做。
                // label值 则 对应 传入的 typeFieldName 的值
                .registerSubtype(GsonInfo.class,"A-one")
                                .registerSubtype(UserInfo.class,"A-two");

        gson = new GsonBuilder().registerTypeAdapterFactory(typeFactory).create();
        GsonObj g = gson.fromJson(json,GsonObj.class);
        System.out.println(g);
    }

    /**
     * 若反序列化中其本身 含有 type 属性 --
     *  而 当前type 又不是唯一固定对应值
     *  则必须定制一个 唯一属性 来对应 相应的片段json 才可以正确解析
     */


}

对于接口的多态的测试

package sun.rain.amazing.gson.ex;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.Test;
import sun.rain.amazing.gson.extra.BaseInfo;
import sun.rain.amazing.gson.extra.IGson;
import sun.rain.amazing.gson.extra.RuntimeTypeAdapterFactory;
import sun.rain.amazing.gson.extra.extend.GsonInfo;
import sun.rain.amazing.gson.extra.extend.GsonObj;
import sun.rain.amazing.gson.extra.extend.UserInfo;
import sun.rain.amazing.gson.extra.impl.GsonOne;
import sun.rain.amazing.gson.extra.impl.GsonTwo;
import sun.rain.amazing.gson.extra.impl.Result;

import java.util.ArrayList;
import java.util.List;

/**
 * @author sunRainAmazing
 */
public class ImplGsonTest {

    private Gson gson = new Gson();

    /**
     * {"name":"Kent","iGsonList":[{"idOne":101,"uuid":"120-365"},
     *     {"idTwo":102,"name":"tomcat"}]}
     *
     * java.lang.RuntimeException:
     *   Unable to invoke no-args constructor for interface
     *        sun.rain.amazing.gson.extra.IGson.
     *
     * 可以被序列化 --- 但是 不能被反序列化
   */
    @Deprecated
    @Test
    public void testGsonImpl(){
        IGson go = new GsonOne(101,"120-365");
        IGson to = new GsonTwo(102,"tomcat");
        Result gos = new Result("Kent",go,to);
        String json = gson.toJson(gos);
        System.out.println(json);
        // Failed to invoke public sun.rain.amazing.gson.extra.BaseInfo() with no args
        Result g = gson.fromJson(json,Result.class);
        System.out.println(g);
    }

    /**
     {"name":"Kent",
     "iGson1":{
        "idOne":101,"uuid":"120-365"},
     "iGson2":{
        "idTwo":102,"name":"tomcat"}}

     -- 改成
     {"name":"Kent",
     "iGson1":{
         "type":"A",
         "idOne":101,"uuid":"120-365"},
     "iGson2":{
         "type":"B",
         "idTwo":102,"name":"tomcat"}}

    ==========================================

     GsonTwo(idTwo=102, name=tomcat)
     Result(name=Kent, iGson1=GsonOne(idOne=101, uuid=120-365),
        iGson2=GsonTwo(idTwo=102, name=tomcat))
     */
    @Test
    public void testGsonImpl1(){
        String json = "{\"name\":\"Kent\",\n" +
                "     \"iGson1\":{\n" +
                "     \"type\":\"A\",\n" +
                "     \"idOne\":101,\"uuid\":\"120-365\"},\n" +
                "     \"iGson2\":{\n" +
                "     \"type\":\"B\",\n" +
                "     \"idTwo\":102,\"name\":\"tomcat\"}}";

        RuntimeTypeAdapterFactory<IGson> typeFactory = RuntimeTypeAdapterFactory
                                //在这里,您可以指定哪个是父类,哪个字段特定于子类。
                // typeFieldName 的值 默认 为 type -- 采用默认的type
                                .of(IGson.class)
                                // 如果标志等于类名,则可以跳过第二个参数。 仅当类型字段不等于类名时,才需要这样做。
                // label值 则 对应 传入的 typeFieldName 的值
                .registerSubtype(GsonOne.class,"A")
                                .registerSubtype(GsonTwo.class,"B");

        gson = new GsonBuilder()
                .registerTypeAdapterFactory(typeFactory)
                .create();
        Result g = gson.fromJson(json,Result.class);
        System.out.println(g.getIGson2());
        System.out.println(g);

    }

    /**
     * 注意 不能 改成这样的json
     {"name":"Kent","
     iGson1":{
     "type":"A",
     "idOne":101,"uuid":"120-365"},
     "iGson2":{
     "type":"B",
     "idTwo":102,"name":"tomcat"}}
     */
    /**
     *
     * 这是因为 jsoniGson1  key  多了一个 \n (换行符) 和 前置空格
   * GsonTwo(idTwo=102, name=tomcat)
     * Result(name=Kent, iGson1=null, iGson2=GsonTwo(idTwo=102, name=tomcat))
     */
    @Test
    public void testGsonImpl2(){
        String json = "{\"name\":\"Kent\",\"\n" +
                "     iGson1\":{\n" +
                "     \"type\":\"A\",\n" +
                "     \"idOne\":101,\"uuid\":\"120-365\"},\n" +
                "     \"iGson2\":{\n" +
                "     \"type\":\"B\",\n" +
                "     \"idTwo\":102,\"name\":\"tomcat\"}} ";


        RuntimeTypeAdapterFactory<IGson> typeFactory = RuntimeTypeAdapterFactory
                                //在这里,您可以指定哪个是父类,哪个字段特定于子类。
                // typeFieldName 的值 默认 为 type -- 采用默认的type
                                .of(IGson.class)
                                // 如果标志等于类名,则可以跳过第二个参数。 仅当类型字段不等于类名时,才需要这样做。
                // label值 则 对应 传入的 typeFieldName 的值
                .registerSubtype(GsonOne.class,"A")
                                .registerSubtype(GsonTwo.class,"B");

        gson = new GsonBuilder()
                .registerTypeAdapterFactory(typeFactory)
                .create();
        Result g = gson.fromJson(json,Result.class);
        System.out.println(g);

    }


}

猜你喜欢

转载自blog.csdn.net/sunrainamazing/article/details/80952535