ความแตกต่างกับ gson


103

ฉันมีปัญหาในการแยกสตริง json กับ Gson ฉันได้รับคำสั่งมากมาย คำสั่งสามารถเริ่มต้นหยุดคำสั่งประเภทอื่น ๆ โดยปกติฉันมีความหลากหลายและคำสั่ง start / stop สืบทอดจากคำสั่ง

ฉันจะทำให้เป็นอนุกรมกลับไปยังวัตถุคำสั่งที่ถูกต้องโดยใช้ gson ได้อย่างไร

ดูเหมือนว่าฉันได้รับเฉพาะประเภทพื้นฐานนั่นคือประเภทที่ประกาศและไม่เคยเป็นประเภทรันไทม์


คำตอบ:


121

นี่มันสายไปหน่อย แต่วันนี้ฉันต้องทำแบบเดิมทุกประการ ดังนั้นจากการวิจัยของฉันและเมื่อใช้ gson-2.0 คุณจริงๆไม่ต้องการใช้registerTypeHierarchyAdapterวิธี แต่ทางโลกมากขึ้นregisterTypeAdapter และแน่นอนว่าคุณไม่จำเป็นต้องทำinstanceofsหรือเขียนอะแดปเตอร์สำหรับคลาสที่ได้รับ: อะแดปเตอร์เพียงตัวเดียวสำหรับคลาสพื้นฐานหรืออินเทอร์เฟซซึ่งแน่นอนว่าคุณพอใจกับการกำหนดลำดับเริ่มต้นของคลาสที่ได้รับ อย่างไรก็ตามนี่คือรหัส (แพ็กเกจและการนำเข้าที่ถูกลบออก) (มีอยู่ในgithub ):

คลาสพื้นฐาน (อินเทอร์เฟซในกรณีของฉัน):

public interface IAnimal { public String sound(); }

สองคลาสที่ได้รับแมว:

public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}

และสุนัข:

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}

IAnimalAdapter:

public class IAnimalAdapter implements JsonSerializer<IAnimal>, JsonDeserializer<IAnimal>{

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(IAnimal src, Type typeOfSrc,
            JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public IAnimal deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

และชั้นทดสอบ:

public class Test {

    public static void main(String[] args) {
        IAnimal animals[] = new IAnimal[]{new Cat("Kitty"), new Dog("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(IAnimal.class, new IAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, IAnimal.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            IAnimal animal2 = gsonExt.fromJson(animalJson, IAnimal.class);
            System.out.println(animal2.sound());
        }
    }
}

เมื่อคุณเรียกใช้ Test :: main คุณจะได้ผลลัพธ์ต่อไปนี้:

serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Cat","INSTANCE":{"name":"Kitty"}}
Kitty : "meaow"
serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Dog","INSTANCE":{"name":"Brutus","ferocity":5}}
Brutus : "bark" (ferocity level:5)

ฉันได้ทำข้างต้นแล้วโดยใช้เมธอดregisterTypeHierarchyAdapterด้วย แต่ดูเหมือนว่าจะต้องใช้คลาส DogAdapter และ CatAdapter serializer / deserializer แบบกำหนดเองซึ่งเป็นความเจ็บปวดในการรักษาทุกครั้งที่คุณต้องการเพิ่มฟิลด์อื่นให้กับ Dog หรือใน Cat


5
โปรดสังเกตว่าการทำให้เป็นอนุกรมของชื่อคลาสและการดีซีเรียลไลเซชัน (จากอินพุตของผู้ใช้) โดยใช้ Class.forName สามารถนำเสนอผลกระทบด้านความปลอดภัยในบางสถานการณ์ดังนั้นทีม Gson dev จึงไม่สนับสนุน code.google.com/p/google-gson/issues/detail?id=340#c2
Programmer Bruce

4
คุณจัดการอย่างไรเพื่อไม่ให้เกิดการวนซ้ำแบบไม่มีที่สิ้นสุดในการทำให้เป็นอนุกรมคุณเรียกว่า context.serialize (src); ซึ่งจะเรียกใช้อะแดปเตอร์ของคุณอีกครั้ง นี่คือสิ่งที่เกิดขึ้นในรหัสที่คล้ายกันของฉัน
che javara

6
ไม่ถูกต้อง. วิธีนี้ใช้ไม่ได้ หากคุณเรียกว่า context.serialize ด้วยวิธีใดก็ตามคุณจะจบลงด้วยการเรียกซ้ำแบบไม่มีที่สิ้นสุด ฉันสงสัยว่าทำไมคนถึงโพสต์โดยไม่ทดสอบโค้ดจริงๆ ฉันลองด้วย 2.2.1 ดูจุดบกพร่องที่อธิบายไว้ในstackoverflow.com/questions/13244769/…
che javara

4
@MarcusJuniusBrutus ฉันรันโค้ดของคุณและดูเหมือนว่ามันจะใช้ได้เฉพาะในกรณีพิเศษนี้เท่านั้น - เพราะคุณได้กำหนด IAnimal แบบซูเปอร์อินเทอร์เฟซและ IAnimalAdapter ใช้มัน หากคุณมีเพียงแค่ 'แมว' คุณจะได้รับปัญหาการเรียกซ้ำที่ไม่สิ้นสุด ดังนั้นวิธีนี้จึงยังใช้ไม่ได้ในกรณีทั่วไป - เฉพาะเมื่อคุณสามารถกำหนดอินเทอร์เฟซทั่วไปได้ ในกรณีของฉันไม่มีส่วนต่อประสานดังนั้นฉันจึงต้องใช้วิธีการอื่นกับ TypeAdapterFactory
che javara

2
ผู้ใช้ src.getClass (). getName () แทน src.getClass (). getCanonicalName () โค้ดนี้จะใช้งานได้กับคลาสภายใน / ที่ซ้อนกันด้วย
mR_fr0g

13

ปัจจุบัน Gson มีกลไกในการลงทะเบียน Type Hierarchy Adapterที่รายงานว่าสามารถกำหนดค่าสำหรับการ deserialization polymorphic อย่างง่าย แต่ฉันไม่เห็นว่าเป็นอย่างไรเนื่องจาก Type Hierarchy Adapter ดูเหมือนจะเป็นตัวสร้าง serializer / deserializer / อินสแตนซ์แบบรวม ปล่อยให้รายละเอียดของการสร้างอินสแตนซ์ไว้ที่ coder โดยไม่ต้องให้การลงทะเบียนประเภท polymorphic ที่แท้จริง

ดูเหมือนว่าในไม่ช้า Gson จะมีการแยกสารย่อยRuntimeTypeAdapterสลายโพลีมอร์ฟิกที่ง่ายขึ้น ดูhttp://code.google.com/p/google-gson/issues/detail?id=231สำหรับข้อมูลเพิ่มเติม

หากไม่สามารถใช้งานใหม่RuntimeTypeAdapterได้และคุณต้องใช้ Gson ฉันคิดว่าคุณจะต้องม้วนโซลูชันของคุณเองลงทะเบียน deserializer แบบกำหนดเองไม่ว่าจะเป็น Type Hierarchy Adapter หรือ Type Adapter ต่อไปนี้เป็นตัวอย่างหนึ่ง

// output:
//     Starting machine1
//     Stopping machine2

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

public class Foo
{
  // [{"machine_name":"machine1","command":"start"},{"machine_name":"machine2","command":"stop"}]
  static String jsonInput = "[{\"machine_name\":\"machine1\",\"command\":\"start\"},{\"machine_name\":\"machine2\",\"command\":\"stop\"}]";

  public static void main(String[] args)
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    CommandDeserializer deserializer = new CommandDeserializer("command");
    deserializer.registerCommand("start", Start.class);
    deserializer.registerCommand("stop", Stop.class);
    gsonBuilder.registerTypeAdapter(Command.class, deserializer);
    Gson gson = gsonBuilder.create();
    Command[] commands = gson.fromJson(jsonInput, Command[].class);
    for (Command command : commands)
    {
      command.execute();
    }
  }
}

class CommandDeserializer implements JsonDeserializer<Command>
{
  String commandElementName;
  Gson gson;
  Map<String, Class<? extends Command>> commandRegistry;

  CommandDeserializer(String commandElementName)
  {
    this.commandElementName = commandElementName;
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    gson = gsonBuilder.create();
    commandRegistry = new HashMap<String, Class<? extends Command>>();
  }

  void registerCommand(String command, Class<? extends Command> commandInstanceClass)
  {
    commandRegistry.put(command, commandInstanceClass);
  }

  @Override
  public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  {
    try
    {
      JsonObject commandObject = json.getAsJsonObject();
      JsonElement commandTypeElement = commandObject.get(commandElementName);
      Class<? extends Command> commandInstanceClass = commandRegistry.get(commandTypeElement.getAsString());
      Command command = gson.fromJson(json, commandInstanceClass);
      return command;
    }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }
  }
}

abstract class Command
{
  String machineName;

  Command(String machineName)
  {
    this.machineName = machineName;
  }

  abstract void execute();
}

class Stop extends Command
{
  Stop(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Stopping " + machineName);
  }
}

class Start extends Command
{
  Start(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Starting " + machineName);
  }
}

หากคุณสามารถเปลี่ยน API ได้โปรดทราบว่าปัจจุบันแจ็คสันมีกลไกในการแยกส่วนย่อยของโพลีมอร์ฟิกที่ค่อนข้างง่าย ฉันโพสต์ตัวอย่างไว้ที่programmerbruce.blogspot.com/2011/05/…
Programmer Bruce

RuntimeTypeAdapterตอนนี้เสร็จสมบูรณ์แล้วน่าเสียดายที่มันยังไม่ได้อยู่ใน Gson core :-(
Jonathan

8

Marcus Junius Brutus มีคำตอบที่ดี (ขอบคุณ!) ในการขยายตัวอย่างของเขาคุณสามารถทำให้คลาสอะแด็ปเตอร์ของเขาเป็นแบบทั่วไปเพื่อทำงานกับอ็อบเจ็กต์ทุกประเภท (ไม่ใช่แค่ IAnimal) โดยมีการเปลี่ยนแปลงต่อไปนี้:

class InheritanceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T>
{
....
    public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context)
....
    public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
....
}

และในชั้นทดสอบ:

public class Test {
    public static void main(String[] args) {
        ....
            builder.registerTypeAdapter(IAnimal.class, new InheritanceAdapter<IAnimal>());
        ....
}

1
หลังจากใช้วิธีแก้ปัญหาของเขาแล้วความคิดต่อไปของฉันคือการทำสิ่งนี้ :-)
David Levy

7

GSON มีกรณีทดสอบที่ค่อนข้างดีที่นี่ซึ่งแสดงวิธีกำหนดและลงทะเบียนอะแดปเตอร์ลำดับชั้นประเภท

http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739

ในการใช้ให้ทำสิ่งนี้:

    gson = new GsonBuilder()
          .registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor())
          .create();

วิธีการทำให้เป็นอนุกรมของอะแด็ปเตอร์อาจเป็นการตรวจสอบ if-else แบบเรียงซ้อนว่าเป็นแบบอนุกรม

    JsonElement result = new JsonObject();

    if (src instanceof SliderQuestion) {
        result = context.serialize(src, SliderQuestion.class);
    }
    else if (src instanceof TextQuestion) {
        result = context.serialize(src, TextQuestion.class);
    }
    else if (src instanceof ChoiceQuestion) {
        result = context.serialize(src, ChoiceQuestion.class);
    }

    return result;

Deserializing ค่อนข้างแฮ็ค ในตัวอย่างการทดสอบหน่วยจะตรวจสอบการมีอยู่ของแอตทริบิวต์การเล่าเรื่องเพื่อตัดสินใจว่าจะ deserialized ไปยังคลาสใด หากคุณสามารถเปลี่ยนแหล่งที่มาของวัตถุที่คุณกำลังทำให้เป็นอนุกรมได้คุณสามารถเพิ่มแอตทริบิวต์ "classType" ให้กับแต่ละอินสแตนซ์ที่เก็บ FQN ของชื่อคลาสอินสแตนซ์ นี่เป็นเรื่องที่ไม่เน้นวัตถุมากนัก


4

Google ได้เปิดตัวRuntimeTypeAdapterFactoryของตัวเองเพื่อจัดการความหลากหลาย แต่น่าเสียดายที่มันไม่ได้เป็นส่วนหนึ่งของ gson core (คุณต้องคัดลอกและวางคลาสในโปรเจ็กต์ของคุณ)

ตัวอย่าง:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

ที่นี่ฉันได้โพสต์ตัวอย่างการทำงานเต็มรูปแบบโดยใช้โมเดล Animal, Dog and Cat

ฉันคิดว่าดีกว่าที่จะพึ่งพาอะแดปเตอร์นี้แทนที่จะนำมาใช้ใหม่ตั้งแต่ต้น


2

เวลาผ่านไปนานมาก แต่ฉันไม่พบวิธีแก้ปัญหาออนไลน์ที่ดีจริงๆ .. นี่คือข้อผิดพลาดเล็กน้อยในการแก้ปัญหาของ @ MarcusJuniusBrutus ที่หลีกเลี่ยงการวนซ้ำที่ไม่สิ้นสุด

ใช้ deserializer เหมือนเดิม แต่เอา serializer ออก -

public class IAnimalAdapter implements JsonDeSerializer<IAnimal> {
  private static final String CLASSNAME = "CLASSNAME";
  private static final String INSTANCE  = "INSTANCE";

  @Override
  public IAnimal deserialize(JsonElement json, Type typeOfT,
        JsonDeserializationContext context) throws JsonParseException  {
    JsonObject jsonObject =  json.getAsJsonObject();
    JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
    String className = prim.getAsString();

    Class<?> klass = null;
    try {
        klass = Class.forName(className);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        throw new JsonParseException(e.getMessage());
    }
    return context.deserialize(jsonObject.get(INSTANCE), klass);
  }
}

จากนั้นในชั้นเรียนเดิมของคุณให้เพิ่มฟิลด์ด้วย@SerializedName("CLASSNAME"). ตอนนี้เคล็ดลับคือการเริ่มต้นสิ่งนี้ในตัวสร้างของคลาสพื้นฐานดังนั้นให้อินเทอร์เฟซของคุณเป็นคลาสนามธรรม

public abstract class IAnimal {
  @SerializedName("CLASSNAME")
  public String className;

  public IAnimal(...) {
    ...
    className = this.getClass().getName();
  }
}

เหตุผลที่ไม่มีการเรียกซ้ำอนันต์นี่คือการที่เราผ่านชั้นรันไทม์ที่เกิดขึ้นจริง (เช่นสุนัขไม่ IAnimal) context.deserializeเพื่อ สิ่งนี้จะไม่เรียกอะแดปเตอร์ประเภทของเราตราบใดที่เราใช้registerTypeAdapterและไม่ใช้registerTypeHierarchyAdapter


2

คำตอบที่อัปเดต - ส่วนที่ดีที่สุดของคำตอบอื่น ๆ ทั้งหมด

ฉันกำลังอธิบายวิธีแก้ปัญหาสำหรับกรณีการใช้งานที่หลากหลายและจะจัดการกับปัญหาการเรียกซ้ำที่ไม่สิ้นสุดเช่นกัน

  • กรณีที่ 1: คุณอยู่ในการควบคุมของการเรียนคือคุณจะได้รับการเขียนของคุณเองCat, Dogการเรียนเช่นเดียวกับIAnimalอินเตอร์เฟซ คุณสามารถทำตามวิธีแก้ปัญหาโดย @ marcus-junius-brutus (คำตอบยอดนิยม)

    จะไม่มีการเรียกซ้ำไม่สิ้นสุดหากมีอินเทอร์เฟซพื้นฐานทั่วไปเป็น IAnimal

    แต่ถ้าฉันไม่ต้องการใช้IAnimalอินเทอร์เฟซหรือส่วนต่อประสานดังกล่าวจะเป็นอย่างไร

    จากนั้น @ marcus-junius-brutus (คำตอบที่ได้รับคะแนนสูงสุด) จะสร้างข้อผิดพลาดซ้ำไม่สิ้นสุด ในกรณีนี้เราสามารถทำสิ่งต่างๆดังต่อไปนี้

    เราจะต้องสร้างตัวสร้างการคัดลอกภายในคลาสพื้นฐานและคลาสย่อยของ wrapper ดังนี้:

.

// Base class(modified)
public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }
    // COPY CONSTRUCTOR
    public Cat(Cat cat) {
        this.name = cat.name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}



    // The wrapper subclass for serialization
public class CatWrapper extends Cat{


    public CatWrapper(String name) {
        super(name);
    }

    public CatWrapper(Cat cat) {
        super(cat);
    }
}

และซีเรียลไลเซอร์สำหรับประเภทCat:

public class CatSerializer implements JsonSerializer<Cat> {

    @Override
    public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {

        // Essentially the same as the type Cat
        JsonElement catWrapped = context.serialize(new CatWrapper(src));

        // Here, we can customize the generated JSON from the wrapper as we want.
        // We can add a field, remove a field, etc.


        return modifyJSON(catWrapped);
    }

    private JsonElement modifyJSON(JsonElement base){
        // TODO: Modify something
        return base;
    }
}

เหตุใดจึงต้องมีตัวสร้างสำเนา

เมื่อคุณกำหนดตัวสร้างการคัดลอกแล้วไม่ว่าคลาสพื้นฐานจะเปลี่ยนไปมากแค่ไหน Wrapper ของคุณจะยังคงมีบทบาทเดิมต่อไป ประการที่สองถ้าเราไม่กำหนดตัวสร้างการคัดลอกและเพียงแค่ซับคลาสคลาสพื้นฐานเราก็จะต้อง "พูดคุย" ในแง่ของคลาสขยายนั่นคือCatWrapper. ค่อนข้างเป็นไปได้ที่ส่วนประกอบของคุณจะพูดในแง่ของคลาสพื้นฐานไม่ใช่ประเภทของกระดาษห่อหุ้ม

มีทางเลือกอื่นที่ง่ายหรือไม่?

แน่นอนว่า Google ได้เปิดตัวแล้ว - นี่คือการRuntimeTypeAdapterFactoryใช้งาน:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

ที่นี่คุณจะต้องแนะนำฟิลด์ที่เรียกว่า "type" ในAnimalและค่าของด้านในเดียวกันDogคือ "dog" Catเป็น "cat"

ตัวอย่างที่สมบูรณ์: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html

  • กรณีที่ 2: คุณไม่ได้อยู่ในการควบคุมของการเรียน คุณเข้าร่วม บริษัท หรือใช้ไลบรารีที่มีการกำหนดคลาสไว้แล้วและผู้จัดการของคุณไม่ต้องการให้คุณเปลี่ยนคลาสใด ๆ - คุณสามารถคลาสย่อยคลาสของคุณและให้พวกเขาใช้อินเทอร์เฟซเครื่องหมายทั่วไป (ซึ่งไม่มีเมธอดใด ๆ ) เช่นAnimalInterface.

    เช่น:

.

// The class we are NOT allowed to modify

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}


// The marker interface

public interface AnimalInterface {
}

// The subclass for serialization

public class DogWrapper  extends Dog implements AnimalInterface{

    public DogWrapper(String name, int ferocity) {
        super(name, ferocity);
    }

}

// The subclass for serialization

public class CatWrapper extends Cat implements AnimalInterface{


    public CatWrapper(String name) {
        super(name);
    }
}

ดังนั้นเราจะใช้CatWrapperแทนCat, DogWrapperแทนDogและ AlternativeAnimalAdapterแทนIAnimalAdapter

// The only difference between `IAnimalAdapter` and `AlternativeAnimalAdapter` is that of the interface, i.e, `AnimalInterface` instead of `IAnimal`

public class AlternativeAnimalAdapter implements JsonSerializer<AnimalInterface>, JsonDeserializer<AnimalInterface> {

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(AnimalInterface src, Type typeOfSrc,
                                 JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public AnimalInterface deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

เราทำการทดสอบ:

public class Test {

    public static void main(String[] args) {

        // Note that we are using the extended classes instead of the base ones
        IAnimal animals[] = new IAnimal[]{new CatWrapper("Kitty"), new DogWrapper("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(AnimalInterface.class, new AlternativeAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, AnimalInterface.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            AnimalInterface animal2 = gsonExt.fromJson(animalJson, AnimalInterface.class);
        }
    }
}

เอาท์พุต:

serialized with the custom serializer:{"CLASSNAME":"com.examples_so.CatWrapper","INSTANCE":{"name":"Kitty"}}
serialized with the custom serializer:{"CLASSNAME":"com.examples_so.DogWrapper","INSTANCE":{"name":"Brutus","ferocity":5}}

1

หากคุณต้องการจัดการ TypeAdapter สำหรับประเภทและอื่น ๆ สำหรับประเภทย่อยของเขาคุณสามารถใช้ TypeAdapterFactory ดังนี้:

public class InheritanceTypeAdapterFactory implements TypeAdapterFactory {

    private Map<Class<?>, TypeAdapter<?>> adapters = new LinkedHashMap<>();

    {
        adapters.put(Animal.class, new AnimalTypeAdapter());
        adapters.put(Dog.class, new DogTypeAdapter());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        TypeAdapter<T> typeAdapter = null;
        Class<?> currentType = Object.class;
        for (Class<?> type : adapters.keySet()) {
            if (type.isAssignableFrom(typeToken.getRawType())) {
                if (currentType.isAssignableFrom(type)) {
                    currentType = type;
                    typeAdapter = (TypeAdapter<T>)adapters.get(type);
                }
            }
        }
        return typeAdapter;
    }
}

โรงงานแห่งนี้จะส่ง TypeAdapter ที่แม่นยำที่สุด


0

หากคุณรวมคำตอบของ Marcus Junius Brutus เข้ากับการแก้ไขของ user2242263 คุณสามารถหลีกเลี่ยงการระบุลำดับชั้นของคลาสขนาดใหญ่ในอะแด็ปเตอร์ของคุณได้โดยกำหนดอะแด็ปเตอร์ของคุณให้ทำงานกับประเภทอินเทอร์เฟซ จากนั้นคุณสามารถจัดเตรียมการใช้งานเริ่มต้นของ toJSON () และ fromJSON () ในอินเทอร์เฟซของคุณ (ซึ่งมีเพียงสองวิธีนี้เท่านั้น) และมีทุกคลาสที่คุณต้องใช้ในการจัดลำดับการใช้อินเทอร์เฟซของคุณ ในการจัดการกับการแคสต์ในคลาสย่อยของคุณคุณสามารถจัดเตรียมเมธอด fromJSON () แบบคงที่ซึ่งแยกค่าสถานะและดำเนินการแคสต์ที่เหมาะสมจากประเภทอินเทอร์เฟซของคุณ สิ่งนี้ใช้งานได้ดีสำหรับฉัน (โปรดระวังเกี่ยวกับคลาส serializing / deserializing ที่มีแฮชแมป - เพิ่มสิ่งนี้เมื่อคุณสร้างอินสแตนซ์ตัวสร้าง gson ของคุณ:

GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();

หวังว่านี่จะช่วยให้ใครบางคนประหยัดเวลาและความพยายาม!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.