【问题标题】:How To Deserialize Generic Types with Moshi?如何使用 Moshi 反序列化泛型类型?
【发布时间】:2017-04-18 10:17:12
【问题描述】:

假设我们有这个 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 基类作为公共字段:

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,然后在提供的 JsonDeserializationContext 上使用 deserialize() 来解析特定的JSON 对象转换为适当的类型。这很好用。

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

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

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

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

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

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

是否有另一种方法来处理我缺少的 Moshi?

【问题讨论】:

    标签: moshi


    【解决方案1】:

    2019-05-25 更新:newer answer 是您的最佳选择。由于历史原因,我将原来的解决方案留在这里。


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

    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 即将发布的 1.4 版本现在更好地支持多态反序列化。 (github.com/square/moshi/issues/89 获取所有详细信息的链接。)感谢您的帖子。我现在会关注 SO 上的 Moshi 标签,所以请解决所有问题!
    • @EricCochran:感谢您提供的信息!但是,除非我误读了问题,否则该问题似乎与多态反序列化无关。
    • 也许最重要的是,JsonAdapter.fromJsonValue(Object) (github.com/square/moshi/pull/234/files) 有助于从地图等中创建这些值。
    【解决方案2】:

    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 中的 __typename 属性将 List&lt;Vehicle&gt; 等内容转换为 JSON,并将其与 "Car" 和 "Boat" 进行比较以创建 @987654331 @ 和 Boat 类。

    【讨论】:

    猜你喜欢
    • 2019-03-05
    • 1970-01-01
    • 2012-10-18
    • 2013-08-26
    • 2023-03-05
    • 1970-01-01
    • 2021-04-09
    • 2014-04-05
    相关资源
    最近更新 更多