Gson обрабатывает неудачный анализ поля как нулевой - PullRequest
0 голосов
/ 24 апреля 2018

Есть ли способ настроить Gson таким образом, чтобы он обрабатывал любой сбойный анализ поля как ноль, а не генерировал исключение синтаксического анализа?В идеале мы могли бы перехватить и записать исключение - но мы хотим, чтобы опция продолжала работать с программой, даже если некоторые поля (или подполя) не анализируются должным образом.

Пример:

Неверно сформировано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 голосов
/ 25 апреля 2018

В 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. Объект, если я не ошибаюсь).

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;

}
0 голосов
/ 24 апреля 2018

Я опубликую решение вашей проблемы, но вам все равно потребуется изменить код на вашей стороне.Например, если вы настроили свойство как объект и получили массив - нет способа правильно отобразить это.Поэтому я бы предложил изменить все в вашем коде на List и написать собственный преобразователь, который создает список с одним элементом при получении объекта.Таким образом, вы будете гибко относиться к тому, что вы получите, но вам также потребуется добавить некоторую логику для решения проблем, когда у вас есть несколько объектов в массиве.Для вашего примера, что бы вы сделали, если бы вы получили 2 собаки?Каково правильное поведение?

Так что я бы сделал это так:

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/ за идею

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...