2017-02-18 1 views
2

У меня есть API, который может возвращать массивы или объекты JSON. Пример JSON объектКак преобразовать динамический ответ JSON с библиотекой Java Gson

{ 
    "id": 1, 
    "name": "name" 
} 

JSON массива:

[ 
    { 
     "id": 1, 
     "name": "name" 
    }, 
    { 
     "id": 1, 
     "name": "name" 
    } 
] 

При отображении ответа объекта JSON к POJO я использую:

MyEntity myEntity = new Gson().fromJson(jsonString, MyEntity.class); 

При отображении ответа JSON массива на массив POJOs Я использую:

MyEntity[] myEntity = new GSON().fromJson(jsonString, MyEntity[].class); 

Как я могу преобразовать эти два ответа в соответствующие типы динамически?

ПРИМЕЧАНИЕ. Я не могу изменить ответ сервера, это общедоступный API.

Спасибо!

EDIT:

Я пытаюсь реализовать метод, который делает это автоматически, но я что-то отсутствует. Метод

public <T> T convertResponseToEntity(Class<T> classOfT) 
{ 
    JsonElement jsonElement = this.gson.fromJson(getResponseAsString(), JsonElement.class); 

    if (jsonElement.isJsonArray()) { 
     Type listType = new TypeToken<T>(){}.getType(); 
     return this.gson.fromJson(getResponseAsString(), listType); 
    } 

    return this.gson.fromJson(getResponseAsString(), (Type) classOfT); 
} 

возвращает список LinkedTreeMap с. Как я могу изменить код, чтобы вернуть тот же контент, что и Object[]?

ответ

1

Как я могу преобразовать эти 2 ответов динамически соответствующего типа?

Это зависит от того, как интерпретировать «соответствующий тип» здесь, потому что это приведет к instanceof или шаблону для посетителей, чтобы получить соответствующий тип, как только вы пытаетесь справиться разобранной-с-JSON объекта каждый раз, когда вам это нужно. Если вы не можете изменить API, вы можете сгладить то, как вы его используете. Один из возможных вариантов здесь - обработка такого ответа, как будто все это список. Даже один объект может обрабатываться как список только с одним элементом (и многие библиотеки работают с последовательностями/списками, имеющими этот факт: Stream API в Java, LINQ в .NET, jQuery в JavaScript и т. Д.).

Предположим, у вас есть следующий MyEntity класс для обработки элементов, полученных из API, нужно:

// For the testing purposes, package-visible final fields are perfect 
// Gson can deal with final fields too 
final class MyEntity { 

    final int id = Integer.valueOf(0); // not letting javac to inline 0 since it's primitive 
    final String name = null; 

    @Override 
    public String toString() { 
     return id + "=>" + name; 
    } 

} 

Далее, давайте создадим тип адаптера, который всегда будет выравнивать «истинные» списки и отдельные объекты, как если бы был список:

final class AlwaysListTypeAdapter<T> 
     extends TypeAdapter<List<T>> { 

    private final TypeAdapter<T> elementTypeAdapter; 

    private AlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) { 
     this.elementTypeAdapter = elementTypeAdapter; 
    } 

    static <T> TypeAdapter<List<T>> getAlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) { 
     return new AlwaysListTypeAdapter<>(elementTypeAdapter); 
    } 

    @Override 
    @SuppressWarnings("resource") 
    public void write(final JsonWriter out, final List<T> list) 
      throws IOException { 
     if (list == null) { 
      out.nullValue(); 
     } else { 
      switch (list.size()) { 
      case 0: 
       out.beginArray(); 
       out.endArray(); 
       break; 
      case 1: 
       elementTypeAdapter.write(out, list.iterator().next()); 
       break; 
      default: 
       out.beginArray(); 
       for (final T element : list) { 
        elementTypeAdapter.write(out, element); 
       } 
       out.endArray(); 
       break; 
      } 
     } 
    } 

    @Override 
    public List<T> read(final JsonReader in) 
      throws IOException { 
     final JsonToken token = in.peek(); 
     switch (token) { 
     case BEGIN_ARRAY: 
      final List<T> list = new ArrayList<>(); 
      in.beginArray(); 
      while (in.peek() != END_ARRAY) { 
       list.add(elementTypeAdapter.read(in)); 
      } 
      in.endArray(); 
      return unmodifiableList(list); 
     case BEGIN_OBJECT: 
      return singletonList(elementTypeAdapter.read(in)); 
     case NULL: 
      return null; 
     case END_ARRAY: 
     case END_OBJECT: 
     case NAME: 
     case STRING: 
     case NUMBER: 
     case BOOLEAN: 
     case END_DOCUMENT: 
      throw new MalformedJsonException("Unexpected token: " + token); 
     default: 
      // A guard case: what if Gson would add another token someday? 
      throw new AssertionError("Must never happen: " + token); 
     } 
    } 

} 

Gson TypeAdapter предназначены для работы в потоковом режиме, таким образом, они дешевы с точки зрения эффективности, но не так просто в реализации. Вышеуказанный метод write() реализован только ради того, чтобы не помещать throw new UnsupportedOperationException(); (я предполагаю, что вы только читаете этот API, но не знаете, может ли этот API использовать запросы на модификацию «любой элемент или список»).Теперь необходимо создать тип адаптер завод, чтобы позволить Gson подобрать адаптер правильного типа для каждого конкретного типа:

final class AlwaysListTypeAdapterFactory 
     implements TypeAdapterFactory { 

    private static final TypeAdapterFactory alwaysListTypeAdapterFactory = new AlwaysListTypeAdapterFactory(); 

    private AlwaysListTypeAdapterFactory() { 
    } 

    static TypeAdapterFactory getAlwaysListTypeAdapterFactory() { 
     return alwaysListTypeAdapterFactory; 
    } 

    @Override 
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) 
      throws IllegalArgumentException { 
     if (List.class.isAssignableFrom(typeToken.getRawType())) { 
      final Type elementType = getElementType(typeToken); 
      // Class<T> instances can be compared with == 
      final TypeAdapter<?> elementTypeAdapter = elementType == MyEntity.class ? gson.getAdapter(MyEntity.class) : null; 
      // Found supported element type adapter? 
      if (elementTypeAdapter != null) { 
       @SuppressWarnings("unchecked") 
       final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) getAlwaysListTypeAdapter(elementTypeAdapter); 
       return castTypeAdapter; 
      } 
     } 
     // Not a type that can be handled? Let Gson pick a more appropriate one itself 
     return null; 
    } 

    // Attempt to detect the list element type 
    private static Type getElementType(final TypeToken<?> typeToken) { 
     final Type listType = typeToken.getType(); 
     return listType instanceof ParameterizedType 
       ? ((ParameterizedType) listType).getActualTypeArguments()[0] 
       : Object.class; 
    } 

} 

И как она используется после того, как все:

private static final Type responseItemListType = new TypeToken<List<MyEntity>>() { 
}.getType(); 

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

public static void main(final String... args) { 
    test(""); 
    test("{\"id\":1,\"name\":\"name\"}"); 
    test("[{\"id\":1,\"name\":\"name\"},{\"id\":1,\"name\":\"name\"}]"); 
    test("[]"); 
} 

private static void test(final String incomingJson) { 
    final List<MyEntity> list = gson.fromJson(incomingJson, responseItemListType); 
    System.out.print("LIST="); 
    System.out.println(list); 
    System.out.print("JSON="); 
    gson.toJson(list, responseItemListType, System.out); // no need to create an intermediate string, let it just stream 
    System.out.println(); 
    System.out.println("-----------------------------------"); 
} 

Выход:

LIST=null 
JSON=null 
----------------------------------- 
LIST=[1=>name] 
JSON={"id":1,"name":"name"} 
----------------------------------- 
LIST=[1=>name, 1=>name] 
JSON=[{"id":1,"name":"name"},{"id":1,"name":"name"}] 
----------------------------------- 
LIST=[] 
JSON=[] 
----------------------------------- 
1

Просто разобрать его в JsonElement и проверить фактический тип элемента:

Gson g = new Gson(); 
JsonParser parser = new JsonParser(); 
JsonElement e = parser.parse(new StringReader(jsonString)); 
if(e instanceof JsonObject) { 
    MyEntity myEntity = g.fromJson(e, MyEntity.class); 
} else { 
    MyEntity[] myEntity = g.fromJson(e, MyEntity[].class); 
} 
Смежные вопросы