Как и чем парсить Json на Java?

221
02 июня 2018, 14:10

Часто возникает потребность работы с Json, в частности его чтения и парсинга. В Java обычно ты знаешь с каким типом переменных работаешь, а при парсинге Json смущает то, что тип полей может быть любой.

Какие есть способы разбора Json? Как это делать?

Вот, допустим, как достать данные из Json, представленного ниже?

{
    "firstName": "Json",
    "lastName": "Smith",
    "age": 30,
    "address": {
        "streetAddress": "666 1nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "542 666-1234"
        },
        {
            "type": "fax",
            "number": "653 666-4567" 
        }
    ],
    "friends": [
        {
            "firstName": "Test",
            "lastName": "Snow",
            "age": 20,
            "phoneNumbers": [
                {
                    "type": "home",
                    "number": "141 111-1234"
                }
            ],
            "friends": [
                {
                    "firstName": "UnknownFirstName",
                    "lastName": "UnknownLastName",
                    "age": 999,
                    "phoneNumbers": [
                        {
                            "type": "home",
                            "number": "000 000-0000"
                        }
                    ]
                }
            ]
        },
        {
            "firstName": "Flash",
            "lastName": "Tompson",
            "age": 23,
            "phoneNumbers": [
                {
                    "type": "home",
                    "number": "999 111-1234"
                }
            ]
        }
    ]
}
Answer 1

Достать данные можно разными способами и, конечно, зависит от задач. Попробую рассмотреть некоторые варианты разбора Json.

Заметка: для каждого из примеров для парсинга будет взят Json из вопроса, чтобы зря не копировать в ответ.

Simple Json

Где взять: здесь / репозиторий на github / или через Maven и пр.

Это самый примитивный способ. По сути, всё, что тут есть - это JSONObject и JSONArray.

  • JSONArray может включать в себя несколько объектов JSONObject, его можно обходить циклом на каждой итерации получая объект JSONObject.
  • JSONObject - объект, из которого можно доставать его отдельные свойства.

Я бы использовал его для небольших Json строк, где не надо сильно заморачиваться или если не лень писать свой класс-обработчик на основе кода, который продемонстрирован ниже:

// Считываем json
Object obj = new JSONParser().parse(jsonString); // Object obj = new JSONParser().parse(new FileReader("JSONExample.json"));
// Кастим obj в JSONObject
JSONObject jo = (JSONObject) obj;
// Достаём firstName and lastName
String firstName = (String) jo.get("firstName"); 
String lastName = (String) jo.get("lastName");
System.out.println("fio: " + firstName + " " + lastName);
// Достаем массив номеров
JSONArray phoneNumbersArr = (JSONArray) jo.get("phoneNumbers");
Iterator phonesItr = phoneNumbersArr.iterator();
System.out.println("phoneNumbers:");
// Выводим в цикле данные массива
while (phonesItr.hasNext()) {
    JSONObject test = (JSONObject) phonesItr.next();
    System.out.println("- type: " + test.get("type") + ", phone: " + test.get("number"));
}

Остальная работа с вложенными массивами аналогична. Можно складывать в List, Map и пр.

GSON

Где взять: здесь / репозиторий на github / или через Maven и пр.

Документация: http://www.studytrails.com/java/json/java-google-json-introduction/

Позволяет парсить Json также, как и Json-simple, т.е. используя JSONObject и JSONArray (см. документацию), но имеет более мощный инструмент парсинга. Достаточно создать классы, которые повторяют структуру Json'а. Для парсинга Json из вопроса создадим классы:

class Person {
    public String firstName;
    public String lastName;
    public int age;
    public Address address;
    public List<Phones> phoneNumbers;
    public List<Person> friends;
}
class Address {
    public String streetAddress;
    public String city;
    public String state;
    public int postalCode;
}
class Phones {
    public String type;
    public String number;
}

Теперь достаточно написать:

Gson g = new Gson();
Person person = g.fromJson(jsonString, Person.class);

Всё! Магия! Чудо! Теперь в person лежит объект с типом Person, в котором находятся данные именно с теми типами, которые были указаны в созданных классах! Теперь можно работать с любым типом, как это привыкли всегда делать: String, Integer, List, Map и всё остальное.

// Выведет фамилии всех друзей с их телефонами
for (Person friend : person.friends) {
    System.out.print(friend.lastName);
    for (Phones phone : friend.phoneNumbers) {
        System.out.println(" - phone type: " + phone.type + ", phone number : " + phone.number);
    }
}
// output:
// Snow - phone type: home, phone number : 141 111-1234
// Tompson - phone type: home, phone number : 999 111-1234

Пример парсинга в Map:

...... JSON для разбора:

{   
	"2":{   
		"sessions":[   
			{   
				"time":"13:00", 
				"price":"410" 
			}, 
			{   
				"time":"06:40", 
				"price":"340" 
			}, 
			{   
				"time":"16:50", 
				"price":"370" 
			} 
		], 
		"name":"Кинокис-L", 
		"locate":"Москва, Садовая-Спасская ул., 21, 56", 
		"metro":"Красные ворота" 
	}, 
	"7":{   
		"sessions":[   
			{   
				"time":"06:35", 
				"price":"190" 
			}, 
			{   
				"time":"00:05", 
				"price":"410" 
			} 
		], 
		"name":"Кинокис-V", 
		"locate":"Павелецкая пл., 2, строение 1", 
		"metro":"Павелецкая" 
	}, 
	"8":{   
		"sessions":[   
			{   
				"time":"15:10", 
				"price":"330" 
			} 
		], 
		"name":"Кинокис-J", 
		"locate":"ул. Пречистенка, 40/2", 
		"metro":"Кропоткинская" 
	}, 
	"9":{   
		"sessions":[   
			{   
				"time":"13:00", 
				"price":"600" 
			}, 
			{   
				"time":"08:30", 
				"price":"300" 
			}, 
			{   
				"time":"04:00", 
				"price":"510" 
			}, 
			{   
				"time":"13:15", 
				"price":"340" 
			} 
		], 
		"name":"Кинокис-U", 
		"locate":"Шарикоподшипниковская ул., 24", 
		"metro":"Дубровка" 
	} 
}

...... Классы (POJO):

class Seanse {
    public String name;
    public String locate
    public String metro;
    public List<Sessions> sessions;
}
class Sessions {
    public String time;
    public double price;
}   

...... Сам разбор выглядит так:

Gson g = new Gson();
Type type = new TypeToken<Map<String, Seanse>>(){}.getType();
Map<String, Seanse> myMap = g.fromJson(json, type);

Всё.

Дополнительно в GSON можно использовать аннотации, например: исключить указанные поля при парсинге, поменять имя переменной (например не personFirstName, а fName) и многое другое. Подробнее см. в документации.

Jackson

Где взять: здесь / репозиторий на github / или через Maven и пр.

Документация и примеры: https://github.com/FasterXML/jackson-docs

Как и GSON он также позволяет работать используя JSONObject и JSONArray если это требуется, и тоже умеет парсить на основе предоставленных классов (см. пример ниже).

Аналогично в нем можно указывать дополнительные требования за счет аннотаций, например: не парсить указанные поля, использовать кастомный конструктор класса, поменять имя переменной (например не firstName, а fName) и многое другое. Подробнее см. в документации.

ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(jsonString, Person.class);
System.out.println("My fio: " + person.firstName + " " + person.lastName + " and my friends are: ");
for (Person friend : person.friends) {
    System.out.print(friend.lastName);
    for (Phones phone : friend.phoneNumbers) {
        System.out.println(" - phone type: " + phone.type + ", phone number : " + phone.number);
    }
}
// output:
// My fio: Json Smith and my friends are: 
// Snow - phone type: home, phone number : 141 111-1234
// Tompson - phone type: home, phone number : 999 111-1234

JsonPath

Где взять: через Maven и другие сборщики / репозиторий на github

Относится к так называемым XPath библиотекам. Её суть аналогична xpath в xml, то есть легко получать часть информации из json'а, по указанному пути. А также позволяет фильтровать по условию.

// Выведет все фамилии друзей
List<String> friendsLastnames = JsonPath.read(jsonString, "$.friends[*].lastName");
for (String lastname : friendsLastnames) {
    System.out.println(lastname);
}
// output:
// Snow
// Tompson

Пример с выборкой по условию:

// Поиск друга, которому больше 22 лет
List<String> friendsWithAges = JsonPath
.using(Configuration.defaultConfiguration())
.parse(jsonString)
.read("$.friends[?(@.age > 22)].lastName", List.class);
for (String lastname : friendsWithAges) {
    System.out.println(lastname);
}
// output:  
// Tompson
Answer 2

Еще несколько вариантов

LoganSquare

LoganSquare - основана на Jackson's streaming API. По демонстрируемым тестам работает быстрее GSON и Jackson. Поэтому хорош для Android.

Где взять: репозиторий на github / или через Maven / Gradle и пр.

Использование: https://github.com/bluelinelabs/LoganSquare#usage

  • Классы должны быть помечены аннотацией @JsonObject
  • Поля должны быть помечены аннотацией @JsonField (с различными варианциями входных параметров, например @JsonField(name="message"))
  • Другие предъявляемые требования: https://github.com/bluelinelabs/LoganSquare/blob/development/docs/Models.md

Простенький пример:

@JsonObject
public class Person {
    @JsonField(name="firstName")
    public String firstName;
    @JsonField(name="age")
    public int age;
    public void say() {
        System.out.println();
        System.out.println("My name is " + firstName + " , I'm " + age + " years old!");
    }
}

разбор:

String jsonString = "{\"age\":15,\"firstName\":\"Adam\"}";
Person person = LoganSquare.parse(jsonString, Person.class);
person.say(); // My name is Adam , I'm 18 years old!

Moshi

Moshi is a modern JSON library for Android and Java.

Хорош, как утверждают разработчики, для работы с Android.

Где взять: репозиторий на github / или через Maven / Gradle и пр.

  • Пример разбора Json строки в объект Person:

    Moshi moshi = new Moshi.Builder().build();
    JsonAdapter<Person> jsonAdapter = moshi.adapter(Person.class);
    Person person = jsonAdapter.fromJson(jsonStringPerson);  // В person будут все данные
  • Пример парсинга в Map:

    class Seanse {
        public String name;
        public String locate
        public String metro;
        public List<Sessions> sessions;
    }
    class Sessions {
        public String time;
        public double price;
    }
    public class Main {
        public static void main(String[] args) throws IOException { 
            String jsonStringForMap = "{\"2\":{\"sessions\":[{\"time\":\"13:00\",\"price\":\"410\"},{ \"time\":\"06:40\",\"price\":\"340\"},{  \"time\":\"16:50\",\"price\":\"370\"}],\"name\":\"Кинокис-L\",\"locate\":\"Москва, Садовая-Спасская ул., 21, 56\",\"metro\":\"Красные ворота\"},\"7\":{  \"sessions\":[  {  \"time\":\"06:35\",\"price\":\"190\"},{  \"time\":\"00:05\",\"price\":\"410\"}],\"name\":\"Кинокис-V\",\"locate\":\"Павелецкая пл., 2, строение 1\",\"metro\":\"Павелецкая\"},\"8\":{  \"sessions\":[  {  \"time\":\"15:10\",\"price\":\"330\"}],\"name\":\"Кинокис-J\",\"locate\":\"ул. Пречистенка, 40/2\",\"metro\":\"Кропоткинская\"},\"9\":{  \"sessions\":[  {  \"time\":\"13:00\",\"price\":\"600\"},{  \"time\":\"08:30\",\"price\":\"300\"},{  \"time\":\"04:00\",\"price\":\"510\"},{  \"time\":\"13:15\",\"price\":\"340\"}],\"name\":\"Кинокис-U\",\"locate\":\"Шарикоподшипниковская ул., 24\",\"metro\":\"Дубровка\"}}";
            Moshi moshi = new Moshi.Builder().build();
            Type map = Types.newParameterizedType(Map.class, String.class, Seanse.class);
            JsonAdapter<Map<String, Seanse>> jsonAdapter = moshi.adapter(map);
            Map<String, Seanse> seanseMap = jsonAdapter.fromJson(jsonStringForMap);
        }
    }

Genson

Где взять: здесь / репозиторий на github / или через Maven и пр.

Документация: http://owlike.github.io/genson/Documentation/UserGuide/

За счет создания POJO (создаются классы, которые повторяют структуру Json'а) - парсится объект из строки, распихивая по нужным полям объектов. Есть возможность фильтровать свойства, включить или исключить поля при парсинге, переименовать, возможность работы с аннотациями и пр. Подробнее в документации.

  • Простой парсинг:

    Genson genson = new Genson();
    Person person = genson.deserialize(jsonString, Person.class); // В person будут все данные
  • Разбор в список:

    List<Object> persons = genson.deserialize("[{\"age\":28,\"name\":\"Foo\"}, {\"age\":30,\"name\":\"Bar\"}]", List.class);    
    // persons - список с данными объектов
  • Пример парсинга в Map:

...... JSON для разбора:

{   
	"2":{   
		"sessions":[   
			{   
				"time":"13:00", 
				"price":"410" 
			}, 
			{   
				"time":"06:40", 
				"price":"340" 
			}, 
			{   
				"time":"16:50", 
				"price":"370" 
			} 
		], 
		"name":"Кинокис-L", 
		"locate":"Москва, Садовая-Спасская ул., 21, 56", 
		"metro":"Красные ворота" 
	}, 
	"7":{   
		"sessions":[   
			{   
				"time":"06:35", 
				"price":"190" 
			}, 
			{   
				"time":"00:05", 
				"price":"410" 
			} 
		], 
		"name":"Кинокис-V", 
		"locate":"Павелецкая пл., 2, строение 1", 
		"metro":"Павелецкая" 
	}, 
	"8":{   
		"sessions":[   
			{   
				"time":"15:10", 
				"price":"330" 
			} 
		], 
		"name":"Кинокис-J", 
		"locate":"ул. Пречистенка, 40/2", 
		"metro":"Кропоткинская" 
	}, 
	"9":{   
		"sessions":[   
			{   
				"time":"13:00", 
				"price":"600" 
			}, 
			{   
				"time":"08:30", 
				"price":"300" 
			}, 
			{   
				"time":"04:00", 
				"price":"510" 
			}, 
			{   
				"time":"13:15", 
				"price":"340" 
			} 
		], 
		"name":"Кинокис-U", 
		"locate":"Шарикоподшипниковская ул., 24", 
		"metro":"Дубровка" 
	} 
}

...... Классы (POJO):

class Seanse {
    public String name;
    public String locate
    public String metro;
    public List<Sessions> sessions;
}
class Sessions {
    public String time;
    public double price;
}   

...... Сам парсинг:

String jsonStringForMap = "ТУТ JSON СТРОКА, ОПИСАННАЯ ВЫШЕ";
Genson genson = new Genson();
Map<String, Seanse> seansesMap = genson.deserialize(jsonStringForMap, Map.class);

FastJson

Где взять: через Maven и другие сборщики / репозиторий на github. Непосредственно описание работы с xpath. Осторожно,   ̶н̶е̶н̶о̶р̶м̶а̶т̶и̶в̶н̶а̶я̶ ̶л̶е̶к̶с̶и̶к̶а̶   китайский язык.

Относится к XPath аналогам.

Обычный способ:

Person person = JSON.parseObject(jsonString, Person.class);
int age = person.age;
System.out.println(age); // 30

Применение XPpath

// Выведет все фамилии друзей
List<String> friendsLastnames = (List<String>) JSONPath.eval(person, "$.friends.lastName");
for (String lastname : friendsLastnames) {
    System.out.println(lastname);  // Snow  Tompson
}
// Поиск друга, которому больше 22 лет
List<String> friendsWithAges = (List<String>) JSONPath.eval(person, "$.friends[?(@.age > 22)].lastName");
for (String lastname : friendsWithAges) {
    System.out.println(lastname); // Tompson
}
Answer 3

JSON-P

Поддерживает сериализацию и парсинг JSON без предварительного маппинга в классах:

Maven:

<dependency>
    <groupId>javax.json</groupId>
    <artifactId>javax.json-api</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.json</artifactId>
    <version>1.1.2</version>
</dependency>

Пример разбора строки JSON:

public static void main(String[] args) throws IOException {
    JsonReader reader  =  Json.createReader(new StringReader(jsonString));
    JsonObject jsonObject = reader.readObject();
}

Пример вывода объекта в строку JSON:

public static void main(String[] args) throws IOException {
    System.out.println(prettyPrintJson(jsonObject, 0));
}
public static String prettyPrintJson(JsonObject jsonObject, int indent) {
    String indentStr = getIndentStr(indent);
    String prettyJson = indentStr + "{";
    for (String key : jsonObject.keySet()) {
        prettyJson += "\n";
        prettyJson += indentStr + "  \"" + key + "\": ";
        try {
            JsonArray jsonArray = jsonObject.get(key).asJsonArray();
            prettyJson += "\n" + indentStr + "  [";
            for (JsonValue element : jsonArray) {
                prettyJson += "\n" + prettyPrintJson(element.asJsonObject(), indent + 4);
                prettyJson += ",";
            }
            prettyJson = prettyJson.substring(0, prettyJson.length() - 1);
            prettyJson += "\n" + indentStr + "  ]";
        } catch (Exception e) {
            try {
                prettyJson += "\n" + prettyPrintJson(jsonObject.get(key).asJsonObject(), indent + 2);
            } catch (Exception ee) {
                prettyJson += jsonObject.get(key).toString();
            }
        }
        prettyJson += ",";
    }
    prettyJson = prettyJson.substring(0, prettyJson.length() - 1);
    prettyJson += "\n" + indentStr + "}";
    return prettyJson;
}
public static String getIndentStr(int indent) {
    String indentStr = "";
    for (int i = 0; i < indent; i++) {
        indentStr += " ";
    }
    return indentStr;
}

Вывод:

{
  "firstName": "Json",
  "lastName": "Smith",
  "age": 30,
  "address": 
  {
    "streetAddress": "666 1nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": 10021
  },
  "phoneNumbers": 
  [
    {
      "type": "home",
      "number": "542 666-1234"
    },
    {
      "type": "fax",
      "number": "653 666-4567"
    }
  ],
  "friends": 
  [
    {
      "firstName": "Test",
      "lastName": "Snow",
      "age": 20,
      "phoneNumbers": 
      [
        {
          "type": "home",
          "number": "141 111-1234"
        }
      ],
      "friends": 
      [
        {
          "firstName": "UnknownFirstName",
          "lastName": "UnknownLastName",
          "age": 999,
          "phoneNumbers": 
          [
            {
              "type": "home",
              "number": "000 000-0000"
            }
          ]
        }
      ]
    },
    {
      "firstName": "Flash",
      "lastName": "Tompson",
      "age": 23,
      "phoneNumbers": 
      [
        {
          "type": "home",
          "number": "999 111-1234"
        }
      ]
    }
  ]
}
Answer 4

Здесь расположена общая информация о парсерах, которая может помочь при выборе и понять, что он умеет. Текст и таблица, представленные ниже, взяты из публикации на Habrahabr: Шпаргалка Java программиста 8. Библиотеки для работы с Json, автор статьи @ВеденинВячеслав

Существует следующие способы сериализации и десериализации среди указанных библиотек (от самого простого к самому сложному):

  1. Data bind
  2. Tree Model
  3. Streaming API
  4. Аналоги XPath (дополнительный способ)

Data bind

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

Плюсы: наиболее простой из всех

Минусы: скорость и память. Большинство библиотек использует рефлексию и т.п. методы работы с Java классами (хотя не все), что очевидно не очень быстро. К тому же, весь json файл сразу превращается в Java объекты, что может просто исчерпать всю доступную память, если вы попытаетесь обработать очень большой json.

Вывод: если нет проблем с производительностью, памятью и вы не собираетесь обрабатывать многогигабайтные json'ы скорее всего самый лучший способ.

Tree Model

Данный парсер представляет json в виде Java классов таких как Node или `JsonElement c иерархической структурой, а уже сам программист их обходит и получает из них информацию.

Плюсы: обычно быстрее первого способа и проще третьего

Минусы: уступает Data bind по простоте, плюс ряд библиотек способен генерить классы при Data bind, а не использовать рефлексию, в этом случае то что Tree Model будет быстрее не очевидно, к тому же не решается проблема огромных файлов и ограничения памяти.

Streaming API

Самый низкоуровневый способ, по сути программист сам вручную разбирает токены json'a. Зато никаких ограничений по памяти и в теории максимальная производительность.

Плюсы: производительность и минимальное потребление памяти

Минусы: сложность использования

Аналоги XPath

Не очень подходит, если нужно получит всю информацию из json'a, зато позволяет написав выражение, например $.store.book[*].author получить список всех авторов всех книг из json'a магазина. То есть легко получать часть информации из json'а.

Плюсы: позволяет быстро получить информацию из json'а по сложным критериям

Минусы: не очень подходит, когда нужна все информация из json'а, не работает в обратную сторону на формирования json'ов

Таблица библиотек и способы парсинга, которые они поддерживают:

Способ\Hазвание  Fastjson  Gson  LoganSquare  JSONjava  Moshi  Jackson  Genson  JsonPath SimpleJson
Data bind          Да       Да       Да         -        Да       Да      Да       -         -
Tree Model         -        Да       -          Да       -        Да      -        -         Да
Streaming API      -        Да       -          -        -        Да      -        -         -
Аналоги XPath      Да       -        -          -        -        -       -        Да        -
Генерация классов  -        -        Да         -        -        -       -        -
 для Data bind*
Работает со        Да       Да       Нет        -        Да       Да      Да       -
static inner class**
Обязательность     Нет      Нет      Да         -        Нет      Нет     Нет      -
 аннотаций***

* — Генерация классов для Data bind позволяет сгенерировать классы на стадии компиляции, что в теории должно давать значительный прирост производительности библиотеки,

** — Работает со static inner class имеет смысл только для случая Data bind, возможно ли сериализация и десериализация для случая статических внутренних классов (не статические внутренние классы сериализовать не рекомендуется),

*** — тоже только для случая Data bind можно ли не использовать аннотации или их использование крайне рекомендуется,

READ ALSO
Google calendar api для работы только со своим акаунтов

Google calendar api для работы только со своим акаунтов

Можно ли сразу настроить авторизацию чтобы не запрашивать авторизацию в пользователей, а создавать календари, события на своем аккаунте?

169
Как сравнивать значение в методе?

Как сравнивать значение в методе?

Не понятно каким образом сравнивать значение зарплаты в методе

197
String to ByteBuffer

String to ByteBuffer

Мне нужно в сокет отправить строкуПри этом, в пакете вначале должно идти длина пакета в байтах

195
Поиск элементов в Listview

Поиск элементов в Listview

На разметке активности есть ListView и EditTextПри вводе символов пользователем в ListView должны оставаться только объекты которые содержат введенные...

221