Json распарсить

290
02 мая 2017, 04:53

Возник вопрос насчет разбора JSON. Пока что я получаю данные в виде строки. Я бы хотел распарсить в класс через Gson. Дело в том что название заголовка 179746 меняется. Оно может принимать другое имя, все остальное не меняется. Использую для генерации класса jsonschema2pojo , ну если у нас название заголовка становится 10000, то класс сразу становится не используемым

{
  "status": "ok",
  "meta": {
    "count": 1
  },
  "data": {
    "179746": {
      "leader_id": 3239400
    }
  }
}

Может можно как-то убрать тут 179746, и подставить общий параметр, я не знаю как делать.

  public class Data {
@SerializedName("179746")
@Expose
private com.example._179746 _179746;
public com.example._179746 get179746() {
return _179746;
}
public void set179746(com.example._179746 _179746) {
this._179746 = _179746;
}

Пока что я делаю так

  jsonObject = new JSONObject(getResponse);
                JSONObject jsonObject1 = jsonObject.getJSONObject("data");
                JSONObject json = jsonObject1.getJSONObject("179746");
Answer 1

Если бы jsonschema2pojo не использовали, количество вопросов связанных с Gson на SO резко бы сократилось вдвое. На это есть следующие причины:

  • автоматические генераторы весьма наивно "сочиняют" маппинги, не учитывая динамических структур данных типа List, Map и т.д.;
  • они совсем плохо дружат с дженериками;
  • они часто не делают даже минимального "опережающего" анализа, чтобы определить, что то или иное поле просто могут иметь разные типы (т.е. могут быть полиморфны -- следствие из предыдущей претензии);
  • не уверен на счёт именно того сервиса, но, по крайней мере, настройки по умолчанию генерируют весьма много шума.

Кроме того, в Gson не существует JSON*** -- типов с заглавными "JSON" в имени. Это из org.json, в то время как в Gson всегда -- Json***. Дальше, если вы уж и собрались использовать маппинги, их можно использовать почти во всех случаях, и не смешивать их с сущностями, которые представляют JSON-объекты в памяти в виде древовидных структур (т.е., всё семейство JsonElement). Ещё: для объектов с динамическими именами в Gson существует родная поддержка Map<K,V> (впрочем, это справедливо и для Java, поскольку количество имён не известно заранее на этапе компиляции).

Резюмируя вышесказанное, в этом случае нужно просто написать правильные маппинги.

final class Response<T> {
    final String status = null;
    final Meta meta = null;
    final T data = null;
}
final class Meta {
    final int count = Integer.valueOf(0);
}
final class Leader {
    @SerializedName("leader_id")
    final int leaderId = Integer.valueOf(0);
}

Чем это отличается от того, что генерирует тот сервис:

  • более совершенная поддержка полиморфности (Response<T>);
  • не делается предположений о "зашитых" именах;
  • здесь почти нет @SerializedName и нет @Expose -- с одной стороны это спорно и противоречит "явному, которое лучше неявного"; с другой -- меньше шума;
  • это примитивные "data bags" и здесь нет аксессоров (set***/get***) -- объекты, предназначенные исключительно для переноса данных между компонентами системы (DTO) и которые должны использоваться исключительно сопутствующими компонентами могут опускать это требование хорошего тона, поскольку здесь и инкапсулировать-то нечего;
  • пример JSON-документа в вопросе похож на ответ от сервиса, поэтому поля маппингов можно оградить от изменений, сделав их final -- изменяться же не должно (Gson отлично справляется с классами без конструкторов и умеет изменять final-поля во время исполнения; исключение из такого "паттерна": примитивным типам как int не могут присваиватся значения по-умолчанию типа = 0, потому что компилятор просто заинлайнит это значения, и у Gson не будет возможности изменить поле (точнее, места, в которые эта константа заинлайнена) -- поэтому Integer.valueOf(0) является своего рода хаком, чтобы ослепить компилятор).

Пример использования:

private static final Gson gson = new Gson();
private static final Type leaderMapResponseType = new TypeToken<Response<Map<Integer, Leader>>>() {
}.getType();
public static void main(final String... args)
        throws IOException {
    try ( final Reader reader = getPackageResourceReader(Q660527.class, "response.json") ) {
        final Response<Map<Integer, Leader>> response = gson.fromJson(reader, leaderMapResponseType);
        System.out.println(response.status);
        System.out.println(response.meta.count);
        for ( final Entry<Integer, Leader> e : response.data.entrySet() ) {
            System.out.println(e.getKey() + " => " + e.getValue().leaderId);
        }
    }
}

Инстансы Gson и Type (по крайней мере те, что порождены с использованием TypeToken<T>) могут считаться иммутабельными и потокобезопасными, поэтому их можно закешировать, не прибегая к повторному созданию экземпляров с теми же характеристиками. Пару слов о том, что такое "type token"-ы. В Java из-за специфики реализации дженериков не существует возможности написать Response<Map<String,Leader>>.class, потому что Class<T> не содержат информации об актуальной параметризации за исключением параметризации родительского класса при наследовании. Возможно, было б очень круто, если бы в Java можно было создавать ad-hoc реализации Type/ParameterizedType примерно как Response<Map<String,Leader>>.type, но увы. Как Gson решает эту проблему? Как я сказал выше, параметризация сохраняется при наследовании, поэтому Gson требует хотя бы анонимной реализации класса TypeToken<T>. При создании экземпляра TypeToken<T> Gson имеет достаточно информации о том, как параметризирован Response<T>, и его вызов getType() возвращает уже проанализированную информацию о параметризированном типе, а не просто о классе. Это эквивалентно ручной реализации ParameterizedType, но с помощью предыдущего механизма запись немножко короче и красивее. И да: "type token"-ы не нужны, если например информация о параметризации уже доступна, например, из типа поля в каком-нибудь классе (т.е., обычный private final Map<String, Float> map; уже содержит достаточно информации о типе (класс по прежнему -- просто Map)).

Результат вывода:

ok
1
179746 => 3239400

Answer 2

Может вам сначала получить название через строку и потом его использовать

int startIndex = getResponse.indexOf("\"data\": {")+9;
int endIndex = getResponse.indexOf("\": {", startIndex);
String nameAfterData = getResponse.subString(startIndex, endIndex);

и потом просто использовать имя JSON объекта пр парсинге?

READ ALSO
jQuery Masked Input и vue модальное окно с input

jQuery Masked Input и vue модальное окно с input

Есть два input в vue модальном окне

383
Цикличная смена текста с анимацией (jQuery)

Цикличная смена текста с анимацией (jQuery)

Необходимо, чтобы определенный список текста циклично менялся аккуратной анимацией по времениСейчас вот такая картина, из-за чего список...

406
Растянуть блок на всю высоту Landing page

Растянуть блок на всю высоту Landing page

У меня на Landing'e блок типа хедера находится слева, и он должен продолжаться на весь landingУже пробовал по разному, но кроме фиксированной высоты...

347
Каким образом сделать подобное?

Каким образом сделать подобное?

Подскажите как лучше(профессиональней) сделать подобное?

294