如何使用Moshi反序列化泛型类型?

时间:2016-12-03 17:02:48

标签: moshi

假设我们有这个JSON:

[
  {
    "__typename": "Car",
    "id": "123",
    "name": "Toyota Prius",
    "numDoors": 4
  },
  {
    "__typename": "Boat",
    "id": "4567",
    "name": "U.S.S. Constitution",
    "propulsion": "SAIL"
  }
]

(列表中可能还有更多元素;这只显示两个元素)

我有CarBoat个POJO,它们使用Vehicle基类作为公共字段:

public abstract class Vehicle {
  public final String id;
  public final String name;
}

public class Car extends Vehicle {
  public final Integer numDoors;
}

public class Boat extends Vehicle {
  public final String propulsion;
}

解析此JSON的结果应该是List<Vehicle>。问题是没有JSON解析器开箱即知,__typename是如何区分BoatCar的。

使用Gson,我可以创建JsonDeserializer<Vehicle>来检查__typename字段,确定这是Car还是Boat,然后使用deserialize()在提供的JsonDeserializationContext上将特定的JSON对象解析为适当的类型。这很好。

但是,我正在构建的特定内容应该支持可插入的JSON解析器,我认为我会尝试使用Moshi作为替代解析器。但是,目前Moshi文档中没有很好地解决这个特殊问题,我很难弄清楚如何最好地解决它。

JsonDeserializer<T> is JsonAdapter<T>最接近的类似物。但是,fromJson()会传递JsonReader,其中包含破坏性API。要了解__typename是什么,我必须能够从JsonReader事件中手动解析所有内容。虽然我知道正确的具体类型后,我可以调用adapter() on the Moshi instance来尝试调用现有的Moshi解析逻辑,但我将消耗JsonReader的数据并破坏其提供完整对象描述的能力。

JsonDeserializer<Vehicle>的另一个类似物是@FromJson-annotated method,它返回Vehicle。但是,我无法确定传递给方法的简单方法。我唯一能想到的是创建另一个代表所有可能字段的联合的POJO:

public class SemiParsedKindOfVehicle {
  public final String id;
  public final String name;
  public final Integer numDoors;
  public final String propulsion;
  public final String __typename;
}

然后,从理论上讲,如果我在@FromJson Vehicle rideLikeTheWind(SemiParsedKindOfVehicle rawVehicle)注册为类型适配器的类上有Moshi,则Moshi可能能够将我的JSON对象解析为SemiParsedKindOfVehicle个实例,致电rideLikeTheWind()。在那里,我会查找__typename,识别类型,并自己完全构建CarBoat,然后返回该对象。

虽然可行,但这比Gson方法要复杂得多,而我的Car / Boat场景就在我需要处理的可能数据结构的简单结尾。

还有另一种方法可以用Moshi处理这个问题吗?

2 个答案:

答案 0 :(得分:4)

更新2019-05-25 newer answer是您最好的选择。由于历史原因,我将离开原来的解决方案。

我没有考虑到的一件事是您可以使用泛型类型创建类型适配器,例如Map<String, Object>。鉴于此,您可以创建一个查找VehicleAdapter的{​​{1}}。它将负责完整填充__typenameCar个实例(或者,可选地,将其委托给BoatCar上的Boat的构造函数输入)。因此,这仍然不如Gson的方法那么方便。另外,你必须有一个无所事事的Map<String, Object>方法,否则Moshi拒绝你的类型适配器。但是,否则,它可以工作,正如JUnit4测试类所证明的那样:

@ToJson

答案 1 :(得分:1)

moshi-adapters附加库包含a PolymorphicJsonAdapterFactory class。虽然该库的JavaDocs似乎没有发布,但源代码中确实包含其用法的详细说明。

在我的问题中,示例的设置为:

  private val moshi = Moshi.Builder()
    .add(
      PolymorphicJsonAdapterFactory.of(Vehicle::class.java, "__typename")
        .withSubtype(Car::class.java, "Car")
        .withSubtype(Boat::class.java, "Boat")
    )
    .build()

现在,我们的Moshi对象知道如何根据JSON中的List<Vehicle>属性将类似__typename的内容转换为JSON,并将其与"Car"进行比较,并且"Boat"分别创建CarBoat类。