如何使用 Moshi 反序列化泛型类型?
How To Deserialize Generic Types with Moshi?
假设我们有这个 JSON:
[
{
"__typename": "Car",
"id": "123",
"name": "Toyota Prius",
"numDoors": 4
},
{
"__typename": "Boat",
"id": "4567",
"name": "U.S.S. Constitution",
"propulsion": "SAIL"
}
]
(列表中可能还有更多元素;这里只显示了两个)
我有 Car
和 Boat
POJO,它们对公共字段使用 Vehicle
基础 class:
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
是如何区分 Boat
和 Car
。
使用 Gson,我可以创建一个 JsonDeserializer<Vehicle>
来检查 __typename
字段,确定这是 Car
还是 Boat
,然后使用 deserialize()
在提供的 JsonDeserializationContext
上将特定的 JSON 对象解析为适当的类型。这很好用。
但是,我正在构建的特定东西应该支持可插入 JSON 解析器,我认为我会尝试将 Moshi 作为替代解析器。但是,目前 Moshi 文档中没有很好地涵盖这个特殊问题,我很难找出解决它的最佳方法。
最接近 JsonDeserializer<T>
is JsonAdapter<T>
. However, fromJson()
gets passed a JsonReader
, which has a destructive API. To find out what the __typename
is, I would have to be able to parse everything by hand from the JsonReader
events. While I could call adapter()
on the Moshi
instance 的类比,尝试调用现有的 Moshi 解析逻辑,一旦我知道正确的具体类型,我将消耗 JsonReader
的数据并损坏它能够提供完整的对象描述。
JsonDeserializer<Vehicle>
的另一个类似物是 @FromJson
-annotated method,returns 是 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;
}
然后,理论上,如果我在 class 上有 @FromJson Vehicle rideLikeTheWind(SemiParsedKindOfVehicle rawVehicle)
,我用 Moshi
注册为类型适配器,Moshi 可能能够解析我的 JSON 对象转换为 SemiParsedKindOfVehicle
个实例并调用 rideLikeTheWind()
。在那里,我会查找 __typename
,确定类型,然后自己完全构建 Car
或 Boat
,返回那个对象。
虽然可行,但比 Gson 方法要复杂一些,我的 Car
/Boat
场景是我需要处理的可能数据结构的简单端与.
是否有另一种方法可以用我缺少的 Moshi 来处理这个问题?
更新 2019-05-25: 是您最好的选择。由于历史原因,我将原来的解决方案留在这里。
有一件事我没有考虑到,您可以使用通用类型创建类型适配器,例如 Map<String, Object>
。鉴于此,您可以创建一个查找 __typename
的 VehicleAdapter
。它将负责完全填充 Car
和 Boat
实例(或者,可选地,将其委托给 Car
和 Boat
上的构造函数,将 Map<String, Object>
作为输入)。因此,这仍然不如 Gson 的方法方便。另外,你必须有一个什么都不做的 @ToJson
方法,否则 Moshi 会拒绝你的类型适配器。但是,除此之外,它是有效的,正如这个 JUnit4 测试 class:
所证明的那样
import com.squareup.moshi.FromJson;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.ToJson;
import com.squareup.moshi.Types;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
public class Foo {
static abstract class Vehicle {
public String id;
public String name;
}
static class Car extends Vehicle {
public Integer numDoors;
}
static class Boat extends Vehicle {
public String propulsion;
}
static class VehicleAdapter {
@FromJson
Vehicle fromJson(Map<String, Object> raw) {
String typename=raw.get("__typename").toString();
Vehicle result;
if (typename.equals("Car")) {
Car car=new Car();
car.numDoors=((Double)raw.get("numDoors")).intValue();
result=car;
}
else if (typename.equals("Boat")) {
Boat boat=new Boat();
boat.propulsion=raw.get("propulsion").toString();
result=boat;
}
else {
throw new IllegalStateException("Could not identify __typename: "+typename);
}
result.id=raw.get("id").toString();
result.name=raw.get("name").toString();
return(result);
}
@ToJson
String toJson(Vehicle vehicle) {
throw new UnsupportedOperationException("Um, why is this required?");
}
}
static final String JSON="[\n"+
" {\n"+
" \"__typename\": \"Car\",\n"+
" \"id\": \"123\",\n"+
" \"name\": \"Toyota Prius\",\n"+
" \"numDoors\": 4\n"+
" },\n"+
" {\n"+
" \"__typename\": \"Boat\",\n"+
" \"id\": \"4567\",\n"+
" \"name\": \"U.S.S. Constitution\",\n"+
" \"propulsion\": \"SAIL\"\n"+
" }\n"+
"]";
@Test
public void deserializeGeneric() throws IOException {
Moshi moshi=new Moshi.Builder().add(new VehicleAdapter()).build();
Type payloadType=Types.newParameterizedType(List.class, Vehicle.class);
JsonAdapter<List<Vehicle>> jsonAdapter=moshi.adapter(payloadType);
List<Vehicle> result=jsonAdapter.fromJson(JSON);
assertEquals(2, result.size());
assertEquals(Car.class, result.get(0).getClass());
Car car=(Car)result.get(0);
assertEquals("123", car.id);
assertEquals("Toyota Prius", car.name);
assertEquals((long)4, (long)car.numDoors);
assertEquals(Boat.class, result.get(1).getClass());
Boat boat=(Boat)result.get(1);
assertEquals("4567", boat.id);
assertEquals("U.S.S. Constitution", boat.name);
assertEquals("SAIL", boat.propulsion);
}
}
moshi-adapters
add-on 库包含 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
对象知道如何根据 __typename
属性 中的 List<Vehicle>
to/from JSON JSON,将其与 "Car"
和 "Boat"
进行比较,分别创建 Car
和 Boat
类。
假设我们有这个 JSON:
[
{
"__typename": "Car",
"id": "123",
"name": "Toyota Prius",
"numDoors": 4
},
{
"__typename": "Boat",
"id": "4567",
"name": "U.S.S. Constitution",
"propulsion": "SAIL"
}
]
(列表中可能还有更多元素;这里只显示了两个)
我有 Car
和 Boat
POJO,它们对公共字段使用 Vehicle
基础 class:
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
是如何区分 Boat
和 Car
。
使用 Gson,我可以创建一个 JsonDeserializer<Vehicle>
来检查 __typename
字段,确定这是 Car
还是 Boat
,然后使用 deserialize()
在提供的 JsonDeserializationContext
上将特定的 JSON 对象解析为适当的类型。这很好用。
但是,我正在构建的特定东西应该支持可插入 JSON 解析器,我认为我会尝试将 Moshi 作为替代解析器。但是,目前 Moshi 文档中没有很好地涵盖这个特殊问题,我很难找出解决它的最佳方法。
最接近 JsonDeserializer<T>
is JsonAdapter<T>
. However, fromJson()
gets passed a JsonReader
, which has a destructive API. To find out what the __typename
is, I would have to be able to parse everything by hand from the JsonReader
events. While I could call adapter()
on the Moshi
instance 的类比,尝试调用现有的 Moshi 解析逻辑,一旦我知道正确的具体类型,我将消耗 JsonReader
的数据并损坏它能够提供完整的对象描述。
JsonDeserializer<Vehicle>
的另一个类似物是 @FromJson
-annotated method,returns 是 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;
}
然后,理论上,如果我在 class 上有 @FromJson Vehicle rideLikeTheWind(SemiParsedKindOfVehicle rawVehicle)
,我用 Moshi
注册为类型适配器,Moshi 可能能够解析我的 JSON 对象转换为 SemiParsedKindOfVehicle
个实例并调用 rideLikeTheWind()
。在那里,我会查找 __typename
,确定类型,然后自己完全构建 Car
或 Boat
,返回那个对象。
虽然可行,但比 Gson 方法要复杂一些,我的 Car
/Boat
场景是我需要处理的可能数据结构的简单端与.
是否有另一种方法可以用我缺少的 Moshi 来处理这个问题?
更新 2019-05-25:
有一件事我没有考虑到,您可以使用通用类型创建类型适配器,例如 Map<String, Object>
。鉴于此,您可以创建一个查找 __typename
的 VehicleAdapter
。它将负责完全填充 Car
和 Boat
实例(或者,可选地,将其委托给 Car
和 Boat
上的构造函数,将 Map<String, Object>
作为输入)。因此,这仍然不如 Gson 的方法方便。另外,你必须有一个什么都不做的 @ToJson
方法,否则 Moshi 会拒绝你的类型适配器。但是,除此之外,它是有效的,正如这个 JUnit4 测试 class:
import com.squareup.moshi.FromJson;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.ToJson;
import com.squareup.moshi.Types;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
public class Foo {
static abstract class Vehicle {
public String id;
public String name;
}
static class Car extends Vehicle {
public Integer numDoors;
}
static class Boat extends Vehicle {
public String propulsion;
}
static class VehicleAdapter {
@FromJson
Vehicle fromJson(Map<String, Object> raw) {
String typename=raw.get("__typename").toString();
Vehicle result;
if (typename.equals("Car")) {
Car car=new Car();
car.numDoors=((Double)raw.get("numDoors")).intValue();
result=car;
}
else if (typename.equals("Boat")) {
Boat boat=new Boat();
boat.propulsion=raw.get("propulsion").toString();
result=boat;
}
else {
throw new IllegalStateException("Could not identify __typename: "+typename);
}
result.id=raw.get("id").toString();
result.name=raw.get("name").toString();
return(result);
}
@ToJson
String toJson(Vehicle vehicle) {
throw new UnsupportedOperationException("Um, why is this required?");
}
}
static final String JSON="[\n"+
" {\n"+
" \"__typename\": \"Car\",\n"+
" \"id\": \"123\",\n"+
" \"name\": \"Toyota Prius\",\n"+
" \"numDoors\": 4\n"+
" },\n"+
" {\n"+
" \"__typename\": \"Boat\",\n"+
" \"id\": \"4567\",\n"+
" \"name\": \"U.S.S. Constitution\",\n"+
" \"propulsion\": \"SAIL\"\n"+
" }\n"+
"]";
@Test
public void deserializeGeneric() throws IOException {
Moshi moshi=new Moshi.Builder().add(new VehicleAdapter()).build();
Type payloadType=Types.newParameterizedType(List.class, Vehicle.class);
JsonAdapter<List<Vehicle>> jsonAdapter=moshi.adapter(payloadType);
List<Vehicle> result=jsonAdapter.fromJson(JSON);
assertEquals(2, result.size());
assertEquals(Car.class, result.get(0).getClass());
Car car=(Car)result.get(0);
assertEquals("123", car.id);
assertEquals("Toyota Prius", car.name);
assertEquals((long)4, (long)car.numDoors);
assertEquals(Boat.class, result.get(1).getClass());
Boat boat=(Boat)result.get(1);
assertEquals("4567", boat.id);
assertEquals("U.S.S. Constitution", boat.name);
assertEquals("SAIL", boat.propulsion);
}
}
moshi-adapters
add-on 库包含 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
对象知道如何根据 __typename
属性 中的 List<Vehicle>
to/from JSON JSON,将其与 "Car"
和 "Boat"
进行比较,分别创建 Car
和 Boat
类。