Gson将失败的字段解析视为null

时间:2018-04-24 13:30:09

标签: java gson

有没有办法配置Gson,以便它将任何失败的字段解析视为null而不是抛出一个解析异常?理想情况下,我们可以捕获并记录异常 - 但是我们希望选项继续使用该程序,即使某些字段(或子字段)没有按预期进行解析。

示例:

格式错误的JSON:

{
   "dog": []
}

使用课程:

class Farm {
  public Dog dog;
}

class Dog {
  public String name;
}

Gson gson = new Gson();
Farm oldMcdonald = gson.fromJson(json, Farm.class); // should not throw exception
assertNull(oldMcdonald.dog); // should pass

2 个答案:

答案 0 :(得分:1)

在Gson中,它可以非常简单地实现。 尽管以下解决方案,我猜,似乎在任何情况下都不起作用(例如,原语),如果有必要可以增强它。

final class JsonFailSafeTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory instance = new JsonFailSafeTypeAdapterFactory();

    private JsonFailSafeTypeAdapterFactory() {
    }

    static TypeAdapterFactory get() {
        return instance;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // We can support non-primitive types only
        if ( typeToken.getRawType().isPrimitive() ) {
            return null;
        }
        final TypeAdapter<T> delegateTypeAdapter = gson.getAdapter(typeToken);
        return new JsonFailSafeTypeAdapter<>(delegateTypeAdapter);
    }

    private static final class JsonFailSafeTypeAdapter<T>
            extends TypeAdapter<T> {

        private final TypeAdapter<T> delegateTypeAdapter;

        private JsonFailSafeTypeAdapter(final TypeAdapter<T> delegateTypeAdapter) {
            this.delegateTypeAdapter = delegateTypeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final T value)
                throws IOException {
            delegateTypeAdapter.write(out, value);
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            try {
                return delegateTypeAdapter.read(in);
            } catch ( final MalformedJsonException | RuntimeException ignored ) {
                // Once we get into unexpected JSON token, let's *always* consider a fallback to the default value
                // Well, the default is always `null` anyway, but we'll do more work
                return fallback(in);
            }
        }

        private static <T> T fallback(final JsonReader in)
                throws IOException {
            final JsonToken jsonToken = in.peek();
            switch ( jsonToken ) {
            case BEGIN_ARRAY:
            case BEGIN_OBJECT:
            case NAME:
            case STRING:
            case NUMBER:
            case BOOLEAN:
            case NULL:
                // Assume we're at the beginning of a complex JSON value or a JSON primitive
                in.skipValue();
                break;
            case END_ARRAY:
                // Not sure if we skipValue() can fast-forward this one
                in.endArray();
                break;
            case END_OBJECT:
                // The same
                in.endObject();
                break;
            case END_DOCUMENT:
                // do nothing
                break;
            default:
                throw new AssertionError(jsonToken);
            }
            // Just return null (at least at the moment)
            return null;
        }

    }

}

现在只需注册上面的类型工厂来处理所有类型(如果我没弄错的话,java.lang.Object除外)。

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(JsonFailSafeTypeAdapterFactory.get())
        .create();

public static void main(final String... args)
        throws IOException {
    try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q50002961.class, "farm.json") ) {
        final Farm oldMcdonald = gson.fromJson(jsonReader, Farm.class);
        if ( oldMcdonald.dog != null ) {
            throw new AssertionError();
        }
        System.out.println(oldMcdonald);
    }
}

示例输出:

  

q50002961.Farm@626b2d4a

如果不需要全局注册工厂,另一个选项还是指定目标字段。例如:

final class Farm {

    @JsonAdapter(JsonFailSafeTypeAdapterFactory.class)
    final Dog dog = null;

}

答案 1 :(得分:0)

我会为您的问题发布解决方案,但仍需要您更改您身边的代码。例如,如果您已将属性配置为对象并且接收到数组 - 则无法正确映射该属性。所以我建议将代码中的所有内容更改为List并编写一个自定义映射器,在收到对象时创建一个包含一个元素的列表。这样,您可以灵活地接收所收到的内容,但是当您有多个对象到阵列时,您还需要添加一些逻辑来处理问题。举个例子,如果你有两只狗,你会怎么做?什么是正确的行为?

所以我会这样做:

public class MainClass {

    public static <T> void main(String[] args) throws IOException {
        Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ArrayAdapterFactory()).create();
        // Here I do the opposite - add one dog but expect a collection
        String json = "{ \"dog\": {name=\"Snoopy\"} }"; 
        Farm oldMcdonald = gson.fromJson(json, Farm.class); // should not throw exception
        System.out.println("Dog:"+oldMcdonald.dog.get(0).name); //Works properly
    }
}

class Farm {
    @Expose
    public List<Dog> dog; //All such properties become a list. You handle the situation when there are more than one values
}

class Dog {
    @Expose
    public String name;
}

 class ArrayAdapter<T> extends TypeAdapter<List<T>> {
      private Class<T> adapterclass;

      public ArrayAdapter(Class<T> adapterclass) {
          this.adapterclass = adapterclass;
      }

      public List<T> read(JsonReader reader) throws IOException {

          List<T> list = new ArrayList<T>();

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

          if (reader.peek() == JsonToken.BEGIN_OBJECT) {
              T inning = gson.fromJson(reader, adapterclass);
              list.add(inning);
              // return null; here if you want to return null instead of list with one element

          } else if (reader.peek() == JsonToken.BEGIN_ARRAY) {

              reader.beginArray();
              while (reader.hasNext()) {
                  T inning = gson.fromJson(reader, adapterclass);
                  list.add(inning);
              }
              reader.endArray();

          }

          return list;
      }

      public void write(JsonWriter writer, List<T> value) throws IOException {

      }

    }



 class ArrayAdapterFactory implements TypeAdapterFactory {

  @SuppressWarnings({ "unchecked", "rawtypes" })
  @Override
  public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {

      TypeAdapter<T> typeAdapter = null;

      try {
          if (type.getRawType() == List.class)
              typeAdapter = new ArrayAdapter(
                      (Class) ((ParameterizedType) type.getType())
                              .getActualTypeArguments()[0]);
      } catch (Exception e) {
          e.printStackTrace();
      }

      return typeAdapter;


  }

}

感谢http://sachinpatil.com/blog/2012/07/03/gson/的想法