source

Gson은 다형 객체 목록을 직렬화합니다.

manycodes 2023. 3. 12. 10:57
반응형

Gson은 다형 객체 목록을 직렬화합니다.

Gson을 사용하여 다형성을 수반하는 오브젝트를 JSON으로 시리얼화/디시리얼화하려고 합니다.

시리얼화 코드는 다음과 같습니다.

ObixBaseObj lobbyObj = new ObixBaseObj();
lobbyObj.setIs("obix:Lobby");

ObixOp batchOp = new ObixOp();
batchOp.setName("batch");
batchOp.setIn("obix:BatchIn");
batchOp.setOut("obix:BatchOut");

lobbyObj.addChild(batchOp);

Gson gson = new Gson();
System.out.println(gson.toJson(lobbyObj));

결과는 다음과 같습니다.

 {"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch"}]}

시리얼화는 상속된 멤버의 내용이 누락되어 있는 것을 제외하고 대부분 기능합니다(특히obix:BatchIn그리고.obixBatchout문자열이 없습니다).기본 클래스는 다음과 같습니다.

public class ObixBaseObj  {
    protected String obix;
    private String display;
    private String displayName;
    private ArrayList<ObixBaseObj> children;

    public ObixBaseObj()
    {
        obix = "obj";
    }

    public void setName(String name) {
        this.name = name;
    }
        ...
}

상속된 클래스(ObixOp)의 모양은 다음과 같습니다.

public class ObixOp extends ObixBaseObj {
    private String in;
    private String out;

    public ObixOp() {
        obix = "op";
    }
    public ObixOp(String in, String out) {
        obix = "op";
        this.in = in;
        this.out = out;
    }
    public String getIn() {
        return in;
    }
    public void setIn(String in) {
        this.in = in;
    }
    public String getOut() {
        return out;
    }
    public void setOut(String out) {
        this.out = out;
    }
}

이 경우 어댑터를 사용할 수 있지만 문제는 기본 클래스 유형의 컬렉션을 직렬화하는 것입니다.ObixBaseObj. 이를 계승하는 클래스는 약 25개입니다.어떻게 하면 우아하게 할 수 있을까요?

간단한 솔루션이 있습니다: Gson's RuntimeTypeAdapterFactory (출처)com.google.code.gson:gson-extras:$gsonVersion시리얼라이저를 쓸 필요가 없습니다.이 클래스는 모두 사용할 수 있습니다.코드를 사용하여 다음을 시도해 보십시오.

    ObixBaseObj lobbyObj = new ObixBaseObj();
    lobbyObj.setIs("obix:Lobby");

    ObixOp batchOp = new ObixOp();
    batchOp.setName("batch");
    batchOp.setIn("obix:BatchIn");
    batchOp.setOut("obix:BatchOut");

    lobbyObj.addChild(batchOp);

    RuntimeTypeAdapterFactory<ObixBaseObj> adapter = 
                    RuntimeTypeAdapterFactory
                   .of(ObixBaseObj.class)
                   .registerSubtype(ObixBaseObj.class)
                   .registerSubtype(ObixOp.class);


    Gson gson2=new GsonBuilder().setPrettyPrinting().registerTypeAdapterFactory(adapter).create();
    Gson gson = new Gson();
    System.out.println(gson.toJson(lobbyObj));
    System.out.println("---------------------");
    System.out.println(gson2.toJson(lobbyObj));

}

출력:

{"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch","children":[]}]}
---------------------
{
  "type": "ObixBaseObj",
  "obix": "obj",
  "is": "obix:Lobby",
  "children": [
    {
      "type": "ObixOp",
      "in": "obix:BatchIn",
      "out": "obix:BatchOut",
      "obix": "op",
      "name": "batch",
      "children": []
    }
  ]
}

EDIT: 더 나은 작업 예.

25개 정도의 클래스가 있다고 하셨는데ObixBaseObj.

우리는 새로운 클래스 Gson Utils를 쓰기 시작합니다.

public class GsonUtils {

    private static final GsonBuilder gsonBuilder = new GsonBuilder()
            .setPrettyPrinting();

    public static void registerType(
            RuntimeTypeAdapterFactory<?> adapter) {
        gsonBuilder.registerTypeAdapterFactory(adapter);
    }

    public static Gson getGson() {
        return gsonBuilder.create();
    }

필요할 때마다Gson오브젝트, 호출 대신new Gson()전화드리겠습니다.

GsonUtils.getGson()

다음 코드를 ObixBaseObj에 추가합니다.

public class ObixBaseObj {
    protected String obix;
    private String display;
    private String displayName;
    private String name;
    private String is;
    private ArrayList<ObixBaseObj> children = new ArrayList<ObixBaseObj>();
    // new code
    private static final RuntimeTypeAdapterFactory<ObixBaseObj> adapter = 
            RuntimeTypeAdapterFactory.of(ObixBaseObj.class);

    private static final HashSet<Class<?>> registeredClasses= new HashSet<Class<?>>();

    static {
        GsonUtils.registerType(adapter);
    }

    private synchronized void registerClass() {
        if (!registeredClasses.contains(this.getClass())) {
            registeredClasses.add(this.getClass());
            adapter.registerSubtype(this.getClass());
        }
    }
    public ObixBaseObj() {
        registerClass();
        obix = "obj";
    }

왜냐고요? 왜냐하면 이 수업이나 아이들 수업에서ObixBaseObj인스턴스화 되고, 그 클래스는RuntimeTypeAdapter

자녀 클래스에서는 최소한의 변경만 필요합니다.

public class ObixOp extends ObixBaseObj {
    private String in;
    private String out;

    public ObixOp() {
        super();
        obix = "op";
    }

    public ObixOp(String in, String out) {
        super();
        obix = "op";
        this.in = in;
        this.out = out;
    }

작업 예:

public static void main(String[] args) {

        ObixBaseObj lobbyObj = new ObixBaseObj();
        lobbyObj.setIs("obix:Lobby");

        ObixOp batchOp = new ObixOp();
        batchOp.setName("batch");
        batchOp.setIn("obix:BatchIn");
        batchOp.setOut("obix:BatchOut");

        lobbyObj.addChild(batchOp);



        Gson gson = GsonUtils.getGson();
        System.out.println(gson.toJson(lobbyObj));

    }

출력:

{
  "type": "ObixBaseObj",
  "obix": "obj",
  "is": "obix:Lobby",
  "children": [
    {
      "type": "ObixOp",
      "in": "obix:BatchIn",
      "out": "obix:BatchOut",
      "obix": "op",
      "name": "batch",
      "children": []
    }
  ]
}

도움이 됐으면 좋겠어요.

커스텀 시리얼라이저/디시리얼라이저가 유일한 방법이라고 생각하며, 제가 찾은 가장 컴팩트한 방법을 제안하려고 했습니다.당신의 수업을 이용하지 못해 죄송합니다만, 같은 생각입니다(기본 클래스 1개, 확장 클래스 2개만 원했습니다).

BaseClass.java

public class BaseClass{
    
    @Override
    public String toString() {
        return "BaseClass [list=" + list + ", isA=" + isA + ", x=" + x + "]";
    }
    
    public ArrayList<BaseClass> list = new ArrayList<BaseClass>();
    
    protected String isA="BaseClass"; 
    public int x;
   
 }

Extended Class 1.java

public class ExtendedClass1 extends BaseClass{

    @Override
    public String toString() {
       return "ExtendedClass1 [total=" + total + ", number=" + number
            + ", list=" + list + ", isA=" + isA + ", x=" + x + "]";
    }

    public ExtendedClass1(){
        isA = "ExtendedClass1";
    }
    
    public Long total;
    public Long number;
    
}

Extended Class 2(확장 클래스 2자바

public class ExtendedClass2 extends BaseClass{

    @Override
    public String toString() {
      return "ExtendedClass2 [total=" + total + ", list=" + list + ", isA="
            + isA + ", x=" + x + "]";
    }

    public ExtendedClass2(){
        isA = "ExtendedClass2";
    }
    
    public Long total;
    
}

CustomDeserializer.java

public class CustomDeserializer implements JsonDeserializer<List<BaseClass>> {

    private static Map<String, Class> map = new TreeMap<String, Class>();

    static {
        map.put("BaseClass", BaseClass.class);
        map.put("ExtendedClass1", ExtendedClass1.class);
        map.put("ExtendedClass2", ExtendedClass2.class);
    }

    public List<BaseClass> deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException {

        List list = new ArrayList<BaseClass>();
        JsonArray ja = json.getAsJsonArray();

        for (JsonElement je : ja) {

            String type = je.getAsJsonObject().get("isA").getAsString();
            Class c = map.get(type);
            if (c == null)
                throw new RuntimeException("Unknow class: " + type);
            list.add(context.deserialize(je, c));
        }

        return list;

    }

}

CustomSerializer.java

public class CustomSerializer implements JsonSerializer<ArrayList<BaseClass>> {

    private static Map<String, Class> map = new TreeMap<String, Class>();

    static {
        map.put("BaseClass", BaseClass.class);
        map.put("ExtendedClass1", ExtendedClass1.class);
        map.put("ExtendedClass2", ExtendedClass2.class);
    }

    @Override
    public JsonElement serialize(ArrayList<BaseClass> src, Type typeOfSrc,
            JsonSerializationContext context) {
        if (src == null)
            return null;
        else {
            JsonArray ja = new JsonArray();
            for (BaseClass bc : src) {
                Class c = map.get(bc.isA);
                if (c == null)
                    throw new RuntimeException("Unknow class: " + bc.isA);
                ja.add(context.serialize(bc, c));

            }
            return ja;
        }
    }
}

모든 것을 테스트하기 위해 실행한 코드는 다음과 같습니다.

public static void main(String[] args) {

  BaseClass c1 = new BaseClass();
  ExtendedClass1 e1 = new ExtendedClass1();
  e1.total = 100L;
  e1.number = 5L;
  ExtendedClass2 e2 = new ExtendedClass2();
  e2.total = 200L;
  e2.x = 5;
  BaseClass c2 = new BaseClass();

  c1.list.add(e1);
  c1.list.add(e2);
  c1.list.add(c2);


  List<BaseClass> al = new ArrayList<BaseClass>();

  // this is the instance of BaseClass before serialization
  System.out.println(c1);

  GsonBuilder gb = new GsonBuilder();

  gb.registerTypeAdapter(al.getClass(), new CustomDeserializer());
  gb.registerTypeAdapter(al.getClass(), new CustomSerializer());
  Gson gson = gb.create();

  String json = gson.toJson(c1);
  // this is the corresponding json
  System.out.println(json);

  BaseClass newC1 = gson.fromJson(json, BaseClass.class);

  System.out.println(newC1);

}

다음은 나의 실행입니다.

BaseClass [list=[ExtendedClass1 [total=100, number=5, list=[], isA=ExtendedClass1, x=0], ExtendedClass2 [total=200, list=[], isA=ExtendedClass2, x=5], BaseClass [list=[], isA=BaseClass, x=0]], isA=BaseClass, x=0]
{"list":[{"total":100,"number":5,"list":[],"isA":"ExtendedClass1","x":0},{"total":200,"list":[],"isA":"ExtendedClass2","x":5},{"list":[],"isA":"BaseClass","x":0}],"isA":"BaseClass","x":0}
BaseClass [list=[ExtendedClass1 [total=100, number=5, list=[], isA=ExtendedClass1, x=0], ExtendedClass2 [total=200, list=[], isA=ExtendedClass2, x=5], BaseClass [list=[], isA=BaseClass, x=0]], isA=BaseClass, x=0]

몇 가지 설명: 이 트릭은 시리얼라이저/디시리얼라이저 내부의 다른 Gson에 의해 실행됩니다.저는 그냥 쓰고 있어요isA적절한 클래스를 찾을 수 있습니다.더 빨리 이동하기 위해 지도를 사용하여isA대응하는 클래스에 스트링을 지정합니다.그런 다음 두 번째 Gson 객체를 사용하여 적절한 시리얼화/디시리얼화를 수행합니다.Gson을 여러 개 할당해도 시리얼화/디시리얼화 속도가 느려지지 않도록 정적임을 선언했습니다.

장점 실제로는 이보다 더 많은 코드를 작성하지 않고 Gson에게 모든 작업을 맡깁니다.맵에 새로운 서브클래스를 추가하는 것(예외는 그 점을 상기시킵니다)만 기억하면 됩니다.

단점 맵이 2개 있습니다.맵의 중복을 피하기 위해 실장을 조금 개선할 수 있다고 생각합니다만, 고객(또는 향후의 에디터(있는 경우)에게 맡겼습니다.

객체로 " "를 .TypeAdapter클래스 또는 두 인터페이스를 모두 구현하는 오브젝트로 실험합니다.

이 문제를 해결하는 데 도움이 된 다른 답변에 감사드립니다.는 ★★★★★★★★★★★★★★★★★★★★★★★★★★RuntimeTypeAdapterFactory반사와 함께.

또한 적절하게 설정된 Gson이 사용되었는지 확인하기 위해 도우미 클래스를 만들었습니다.

GsonHelper 클래스 내의 정적 블록 내에서 다음 코드를 사용하여 적절한 유형을 모두 검색 및 등록합니다.JSON-ification을 통과하는 모든 객체는 JSonable의 하위 유형입니다.다음을 변경할 수 있습니다.

  1. in my.project in.Reflections패키지 이름이어야 합니다.
  2. Jsonable.class베이스 클래스입니다.네 것으로 대체해.
  3. 필드에 완전한 표준 이름을 표시하는 것은 좋지만, 필요하지 않은 경우 호출의 해당 부분을 생략하고 하위 유형을 등록할 수 있습니다.입니다.className RuntimeAdapterFactory입니다. ; ; ; ; ; ; ; ; ; 。typesyslog.syslog.syslog.

    private static final GsonBuilder gsonBuilder = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
        .excludeFieldsWithoutExposeAnnotation()
        .setPrettyPrinting();
    
    static {
    Reflections reflections = new Reflections("my.project");
    
    Set<Class<? extends Jsonable>> allTypes = reflections.getSubTypesOf(Jsonable.class);
    for (Class< ? extends Jsonable> serClass : allTypes){
        Set<?> subTypes = reflections.getSubTypesOf(serClass);
        if (subTypes.size() > 0){
            RuntimeTypeAdapterFactory<?> adapterFactory = RuntimeTypeAdapterFactory.of(serClass, "className");
            for (Object o : subTypes ){
                Class c = (Class)o;
                adapterFactory.registerSubtype(c, c.getCanonicalName());
            }
            gsonBuilder.registerTypeAdapterFactory(adapterFactory);
        }
    }
    }
    
    public static Gson getGson() {
        return gsonBuilder.create();
    }
    

주석과 ClassGraph를 사용하여 서브클래스를 검출하고 여러 시리얼라이제이션 스타일(Type Property, Property, Array)을 지원하는 타입 어댑터 팩토리를 만들었습니다.소스 코드와 메이븐 좌표는 github을 참조하십시오.

언급URL : https://stackoverflow.com/questions/19588020/gson-serialize-a-list-of-polymorphic-objects

반응형