如何使用Retrofit2和GSON转换器解决“古怪”的JSON API

mfpqipee  于 2022-11-23  发布在  其他
关注(0)|答案(1)|浏览(253)

我正在为第三方JSON REST API实现一个客户端。在客户端(我的项目),我使用Retrofit和GSON作为库。服务器端似乎是用PHP实现的,这超出了我的控制范围,也就是说,我不能很容易地修复服务器。
服务器频繁地以不同的实际类型来响应相同的形式类型。开箱即用,我在反序列化过程中得到了很多GSON解析/转换异常,因为GSON对正确的类型非常挑剔。
当涉及到反序列化响应和类型转换时,我如何使GSON更健壮呢?我已经找到了注解@JsonAdapter,它可能允许我解决服务器的这些怪癖。但由于这似乎是一个典型的PHP问题(示例如下),我认为可能已经有一个用于GSON的库或外观来解决这些问题。
具体来说,服务器表现出以下特点:

  • 布林值:响应中的布尔值被报告为实际的JSON布尔值,即{ b : true }{ b : false },但也被报告为JSON数字01以及文字字符串"0""1""true""false"。此外,假值也被报告为null(无对象)和文字字符串"null"
  • 数字:响应中的数字报告为实际的JSON数字,即{ n : 42 },但也报告为文字字符串"42"。此外,零值也报告为null(无对象)、文字字符串"null"false(布尔值)或文字字符串"false"
  • 对象数组:如果数组是非空的,一切都很好。响应是{ arr : [ ... ] },GSON很高兴地将其反序列化为Java List<>。空数组是个问题。服务器正确地将空数组报告为[],但也报告为falsenull

上面的列表仅仅是我目前所遇到的奇怪问题的开始。可能还有更多。这就是为什么我在犹豫是否要为GSON编写可能拥有的适配器,因为我担心列表很快就会变得无穷无尽。有没有已经实现了这些典型PHP问题的转换的库?
幸运的是,GSON转换器只需要在反序列化时具有额外的健壮性,也就是说,如果来自服务器的响应被解析为Java对象,那么当Java对象被序列化为JSON并在请求中发送到服务器时,服务器很乐意接受任何类型,只要PHP能够将其转换为所需的类型。

更新

我开始为普通的旧Java类型编写自定义类型适配器QuirkyBooleanQuirkyIntegerintlongboolean)和它们的OO对应部分IntegerLong,到目前为止,这很容易。棘手的部分是实现一个通用的列表适配器,它涵盖了第3项中提到的怪癖(见上文),并且可以正确地反序列化列表中的任何类型。我的问题是Java的类型擦除。也许有人有解决以下问题的方法?
假设以下带注解的POJO用于GSON序列化/反序列化。

public class Container {
  @SerializeName("foos")
  @JsonAdapter( QuirkyListAdapter<Foo>.class )  // Note: No legal Java syntax
  public List<Foo> foos;
  
  @SerializeName("bars")
  @JsonAdapter( QuirkyListAdapter<Bar>.class )  // Note: No legal Java syntax
  public List<Bar> bars;
}

public class Foo {
  @SerializeName("str")
  public String str;
}

public class Bar {
  @SerializeName("n")
  public Integer n;
}

我的QuirkyListAdapter方法正确地将JSON null{}""false反序列化为空列表。

public class QuirkyListAdapter<T> extends TypeAdapter<List<T>> {
  @Override
  public void write( @NonNull JsonWriter out, List<T> listOfT ) throws IOException {
    final Gson gson = new Gson();
    final TypeAdapter<T> typeAdapter = gson.getAdapter( T.class );  // Note: No legal Java syntax
    out.beginArray();
    if( listOfT != null ) {
      for( final T val : listOfT ) {
        typeAdapter.write( out, val );
      }
    }
    out.endArray();
  }

  @Override
  @NonNull
  public List<T> read( @NonNull JsonReader in ) throws IOException {
    final List<T> result = new ArrayList<>();
    final JsonToken peekedToken = in.peek();

    if( peekedToken == JsonToken.NULL ) {
      in.nextNull();
      return result;
    }
    if( peekedToken == JsonToken.BOOLEAN ) {
      if( in.nextBoolean() )
        throw new IllegalStateException( "Expected an empty array encoded as boolean value \"FALSE\" but found \"true\"" );
      return result;
    }
    if( peekedToken == JsonToken.STRING ) {
      final String str = in.nextString();
      if( str.equals( "" ) )
        throw new IllegalStateException( "Expected an empty array encoded as an empty string value but found \"" + str + "\"" );
      return result;
    }
    if( peekedToken == JsonToken.BEGIN_OBJECT ) {
      in.beginObject();
      if( in.peek() != JsonToken.END_OBJECT)
        throw new IllegalStateException( "Expected an empty array encoded as an empty object \"{}\", but object has attributes" );
      in.endObject(); 
    }
    if( peekedToken != JsonToken.BEGIN_ARRAY )
      throw new IllegalStateException( "Expected BEGIN_ARRAY but was " + peekedToken );

    final Gson gson = new Gson();
    final TypeAdapter<T> typeAdapter = gson.getAdapter( T.class );  // Note: No legal Java syntax

    in.beginArray();
    while( in.hasNext() ) {
      result.add( typeAdapter.read( in ) );
    }
    in.endArray();
    return result;
  }
}

如果我分别用FooBar替换泛型T,并实现两个独立的QuirkyFooListAdapterQuirkyBarListAdapter,那么一切都可以正常工作。即使用Object替换T也不是解决方案,尽管它可以编译。但是,在我的古怪的列表适配器中,有一行是gson.getAdapter( .. )。使用gson.getAdapter( Object.class )显然不会返回所需的适配器,而该适配器是反序列化列表中正确对象所必需的。

xmd2e60i

xmd2e60i1#

您不需要那么多类型适配器,因为您可以将通用的反序列化逻辑合并到为后端遇到的每个有问题的文本类型(就像您在问题中提出的那样)设计的类型适配器中。
独立创建的类型适配器,而不是在类型适配器工厂中创建的类型适配器,通常适合简单的情况。工厂提供对共享 contextGson示例(您配置并使用的示例)的访问,并提供用于构建类型适配器的具体类型(这是可以解决“T.class”问题的地方)。
第一个
上面的方法为所有三种情况实现了Template Method设计模式:布尔值、数字和数组(JSON数组,但Java集合,为了简洁起见,我没有包括Java数组适配器)。
它们背后的共同逻辑如下:

  • 类型适配器工厂检查它是否可以处理给定的类型。
  • 如果可以,那么它向Gson请求代理类型适配器来处理。
  • 建立泛型型别配接器,它只会将写入作业委派给原始型别配接器,但读取作业会在存取它的每个子类别中专用化。
  • 每个读取操作都实现简单的JSON令牌窥视,以根据您在问题中描述的特殊情况决定如何进一步处理。

具有以下JSON:

{
    "booleans": [
        true,
        false,
        0,
        1,
        "0",
        "1",
        "true",
        "false",
        null,
        "null"
    ],
    "numbers": [
        42,
        "42",
        null,
        "null",
        false,
        "false"
    ],
    "arrays": [
        [
            "foo",
            "bar"
        ],
        [],
        false,
        null
    ]
}

以下测试通过:
一个

相关问题