helloworlds/3.8-json/readme-russian.md
To Useful Java links project / To Hello World project
Цитата из useful-java-links:
Итак, у нас восемь библиотек для сериализации и десериализации в json, две библиотеки для генерации Java классов по схеме или json файлу, одна библиотека для валидации схемы и два аналога XPath, но для json. Давайте рассмотрим каждую из них.
Существует три основных способа сериализации и десериализации среди указанных библиотек (от самого простого к самому сложному) и один дополнительный:
Давайте рассмотрим с чем их едят:
Data bind самый популярный и простой способ, вы просто указываете класс, который нужно преобразовать в json, может быть часть полей отмечаете аннотациями (а зачастую даже это необязательно), а библиотека сама превращает этот класс и всю его иерархию классов в json. Аналогом при работе с xml будет JAXB (Java Architecture for XML Binding) Плюсы: наиболее простой из всех, по сути главное реализовать только Java классы, более того можно просто сгенерировать Java классы из json'a или json схемы. Минусы: скорость и память. Большинство библиотек использует рефлексию и т.п. методы работы с Java классами (хотя не все), что очевидно не очень быстро. К тому же, весь json файл сразу превращается в Java объекты, что может просто исчерпать всю доступную память, если вы попытаетесь обработать очень большой json. Вывод: если нет проблем с производительностью, памятью и вы не собираетесь обрабатывать многогигабайтные json'ы скорее всего самый лучший способ.
Tree Model - данный парсер представляет json в виде Java классов таких как Node или JsonElement c иерархической структурой, а уже сам программист их обходит и получает из них информацию. Данный способ похож на DOM парсеры в xml. Плюсы: обычно быстрее первого способа и проще третьего, Минусы: уступает Data bind по простоте, плюс ряд библиотек способен генерить классы при Data bind, а не использовать рефлексию, в этом случае то что Tree Model будет быстрее не очевидно, к тому же не решается проблема огромных файлов и ограничения памяти.
Streaming API - самый низкоуровневый способ, по сути программист сам вручную разбирает токены json'a. Зато никаких ограничений по памяти и в теории максимальная производительность.
Плюсы: производительность и минимальное потребление памяти,
Минусы: сложность использования,
Аналоги XPath - дополнительный способ, не очень подходит, если нужно получит всю информацию из json'a, зато позволяет написав выражение $.store.book[*].author и получить список всех авторов всех книг из json'a магазина. То есть легко получать часть информации из json'а.
Плюсы: позволяет быстро получить информацию из json'а по сложным критериям,
Минусы: не очень подходит, когда нужна все информация из json'а, не работает в обратную сторону на формирования json'ов,
Способ | Fastjson | Gson | LoganSquare | JSON java | Moshi | Ig json parser | Jackson | Genson | JsonPath | JSON.simple ---------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
По ссылкам на Да можно найти примеры использования.
Для демонстрации работы библиотек будем использовать следующий json:
jsonString =
{
"message": "Hi",
"place": {
"name": "World"
}
}
И следующие Java классы (в разных примерах могут слегка отличаться наличием аннотаций, если они обязательны):
class Human {
private String message;
private Place place;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Place getPlace() {
return place;
}
public void setPlace(Place place) {
this.place = place;
}
public void say() {
System.out.println();
System.out.println(getMessage() + " , " + getPlace().getName() + "!");
}
}
class Place {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// init class
Place place = new Place();
place.setName("World");
Human human = new Human();
human.setMessage("Hi");
human.setPlace(place);
Как можно увидеть, Java классы всего лишь состоять из двух классов Human и Place, в которых храниться сообщение Hi World!. Json тоже содержит эти два вложенных объекта.
Примеры использования (Data bind):
| Методы | Fastjson | Gson | LoganSquare | Moshi | Ig json parser | Jackson | Genson |
|---|---|---|---|---|---|---|---|
| Инициализация | --- | Gson gson = new Gson() | --- | Moshi moshi = new Moshi. | |||
Builder().build(); JsonAdapter<Human> | |||||||
jsonAdapter = moshi.adapter(Human.class) | --- | ObjectMapper mapper = new ObjectMapper() | Genson genson = new Genson() | ||||
| Генерация json | JSON.toJSONString(human) | gson.toJson(human) | LoganSquare.serialize(human) | jsonAdapter.toJson(human) | Human__JsonHelper.serializeToJson(human) | mapper.writeValueAsString(human) | genson.serialize(human) |
| Парсинг json | JSON.parseObject(jsonString, Human.class) | gson.fromJson(jsonString, Human.class) | LoganSquare.parse(jsonString, Human.class) | jsonAdapter.fromJson(jsonString) | Human__JsonHelper.parseFromJson(jsonString) | mapper.readValue(jsonString, Human.class) | genson.deserialize(jsonString, Human.class) |
Human__JsonHelper - это класс который Ig json parser сгенерировал на этапе компиляции, у LoganSquare так же есть генерации на этапе компиляции, но там классы подключаются "под капотом" внутри LoganSquare.
Давайте рассмотрим примеры подробнее.
// convert to json
String jsonString = JSON.toJSONString(human);
System.out.println("json " + jsonString); // напечатает "json {"message":"Hi","place":{"name":"World"}}"
// convert from json
Human newHuman = JSON.parseObject(jsonString, Human.class);
newHuman.say(); // напечатает "Hi , World!"
// convert to json
Gson gson = new Gson();
String jsonString = gson.toJson(human);
System.out.println("json " + jsonString); // напечатает "json {"message":"Hi","place":{"name":"World"}}"
// convert from json
Human newHuman = gson.fromJson(jsonString, Human.class);
newHuman.say(); // напечатает "Hi , World!"
@JsonObject
public class Human {
@JsonField(name="message")
public String message;
@JsonField(name="place")
public Place place;
....
// convert to json
String jsonString = LoganSquare.serialize(human);
System.out.println("json " + jsonString); // напечатает "json {"place":{"name":"World"},"message":"Hi"}"
// convert from json
Human newHuman = LoganSquare.parse(jsonString, Human.class);
newHuman.say(); // напечатает "Hi , World!"
// convert to json
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Human> jsonAdapter = moshi.adapter(Human.class);
String jsonString = jsonAdapter.toJson(human);
System.out.println("json " + jsonString); // напечатает "json {"message":"Hi","place":{"name":"World"}}"
// convert from json
Human newHuman = jsonAdapter.fromJson(jsonString);
newHuman.say(); // напечатает "Hi , World!"
@JsonType
public class Human {
@JsonField(fieldName="message")
public String message;
@JsonField(fieldName="place")
public Place place;
...
// convert to json
String jsonString = Human__JsonHelper.serializeToJson(human);
System.out.println("json " + jsonString); // напечатает "json {"place":{"name":"World"},"message":"Hi"}"
// convert from json
Human newHuman = Human__JsonHelper.parseFromJson(jsonString);
newHuman.say(); // напечатает "Hi , World!"
// convert to json
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(human);
System.out.println("json " + jsonString); // напечатает "json {"message":"Hi","place":{"name":"World"}}"
// convert from json
Human newHuman = mapper.readValue(jsonString, Human.class);
newHuman.say(); // напечатает "Hi , World!"
// convert to json
String jsonString = new Genson().serialize(human);
System.out.println("json " + jsonString); // напечатает "json {"message":"Hi","place":{"name":"World"}}"
// convert from json
Human newHuman = new Genson().deserialize(jsonString, Human.class);
newHuman.say(); // напечатает "Hi , World!"
Использование Tree Model есть у трех библиотек: Gson, Jackson и Json Java. Давайте посмотрим их реализацию.
Для демонстрации работы библиотек будем использовать тот же json:
jsonString =
{
"message": "Hi",
"place": {
"name": "World"
}
}
Методы парсинга json'a:
| Действие | Gson | Jackson | JSON java | JSON.simple |
|---|---|---|---|---|
| Инициализация | JsonParser parser = new JsonParser() | new ObjectMapper() | - | - |
| Парсинг json'a | parser.parse(<строка>) | mapper.readValue(<строка>, JsonNode.class) | new JSONObject(<строка>) | (JSONObject) JSONValue.parse(<строка>) |
| Получение главного объекта | root.getAsJsonObject() | - | - | - |
| Получение строки | root.get(<имя>).getAsString() | root.get(<имя>).asText() | root.getString(<имя>) | (String) root.get(<имя>) |
| Получение дочернего объекта | root.getAsJsonObject(<имя>) | root.get(<имя>) | root.getJSONObject(<имя>) | (JSONObject) root.get(<имя>) |
Методы генерации json'a:
| Действие | Gson | Jackson | JSON java | JSON.simple |
|---|---|---|---|---|
| Инициализация | - | new ObjectMapper() | - | - |
| Создание главного объекта | new JsonObject() | mapper.createObjectNode() | new JSONObject() | new JSONObject() |
| Добавить строковое поле | root.addProperty(<имя>, <строка>) | root.put(<имя>, <строка>) | root.put(<имя>, <строка>) | root.put(<имя>, <строка>) |
| Добавить дочерний объект | root.add(<имя>, <объект>); | root.putObject(<имя>) | root.put(<имя>, <объект>) | root.put(<имя>, <объект>) |
Примеры:
JsonParser parser = new JsonParser();
JsonElement jsonElement = parser.parse("{\"message\":\"Hi\",\"place\":{\"name\":\"World!\"}}");
JsonObject rootObject = jsonElement.getAsJsonObject(); // чтение главного объекта
String message = rootObject.get("message").getAsString(); // получить поле "message" как строку
JsonObject childObject = rootObject.getAsJsonObject("place"); // получить объект Place
String place = childObject.get("name").getAsString(); // получить поле "name"
System.out.println(message + " " + place); // напечатает "Hi World!"*/
JsonObject rootObject = new JsonObject(); // создаем главный объект
rootObject.addProperty("message", "Hi"); // записываем текст в поле "message"
JsonObject childObject = new JsonObject(); // создаем объект Place
childObject.addProperty("name", "World!"); // записываем текст в поле "name" у объект Place
rootObject.add("place", childObject); // сохраняем дочерний объект в поле "place"
Gson gson = new Gson();
String json = gson.toJson(rootObject); // генерация json строки
System.out.println(json); // напечатает "{"message":"Hi","place":{"name":"World!"}}"
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue("{\"message\":\"Hi\",\"place\":{\"name\":\"World!\"}}", JsonNode.class); // парсинг текста
String message = rootNode.get("message").asText(); // получение строки из поля "message"
JsonNode childNode = rootNode.get("place"); // получаем объект Place
String place = childNode.get("name").asText(); // получаем строку из поля "name"
System.out.println(message + " " + place); // напечатает "Hi World!"
OutputStream outputStream = new ByteArrayOutputStream();
ObjectMapper mapper = new ObjectMapper();
ObjectNode rootNode = mapper.createObjectNode(); // создание главного объекта
rootNode.put("message", "Hi");
ObjectNode childNode = rootNode.putObject("place"); // создание дочернего объекта Place
childNode.put("name", "World!");
mapper.writeValue(outputStream, childNode); // запись json строки в стрим
System.out.println(outputStream.toString()); // напечатает "{"message":"Hi","place":{"name":"World!"}}"
// convert Java to json
JSONObject root = new JSONObject(); // создаем главный объект
root.put("message", "Hi");
JSONObject place = new JSONObject(); // создаем объект Place
place.put("name", "World!");
root.put("place", place); // сохраняем объект Place в поле place
String json = root.toString();
System.out.println(json); // напечатает "{"message":"Hi","place":{"name":"World!"}}"
System.out.println();
// convert json to Java
JSONObject jsonObject = new JSONObject(json); // парсинг json
String message = jsonObject.getString("message");
String name = jsonObject.getJSONObject("place").getString("name");
System.out.println(message + " " + name); // напечатает "Hi World!"
JSONObject obj = (JSONObject) JSONValue.parse(json);
String message = (String) obj.get("message");
place = (JSONObject) obj.get("place");
String name = (String) place.get("name");
System.out.println(message + " " + name);
JSONObject root = new JSONObject();
root.put("message", "Hi");
JSONObject place = new JSONObject();
place.put("name", "World!");
root.put("place", place);
String json = root.toJSONString();
System.out.println(json);
В общем, можно заметить что во всех библиотеках выполняются примерно те же действия, отличаются только названия классов.
Для демонстрации работы библиотек будем использовать все тот же json:
jsonString =
{
"message": "Hi",
"place": {
"name": "World"
}
}
Обычно Streaming API используется крайне редко, только в задачах требующих очень высокой производительности или при очень больших файлах.
Методы парсинга json'a:
| Действие | Gson | Jackson |
|---|---|---|
| Инициализация | - | new JsonFactory() |
| Парсинг json'a | reader = new JsonReader((<input_stream>) | parser = jsonFactory.createParser(<строка>) |
| Проверка есть ли ещё токены | reader.hasNext() | parser.hasCurrentToken() |
| Получение типа токена | reader.peek() | parser.nextToken() |
| Получение следующего токена | reader.nextString() | |
reader.beginObject() | ||
reader.endObject() и т.п. | parser.nextToken() | |
| Пропуск токена | reader.skipValue() | parser.nextToken() |
| Получение строки | reader.nextString() | parser.getText() |
Методы генерации json'a:
| Действие | Gson | Jackson |
|---|---|---|
| Инициализация | writer = new JsonWriter(<output_stream>) | generator = new JsonFactory().createGenerator(<output_stream>, <кодировка>) |
| Токен начала объекта | writer.beginObject() | generator.writeStartObject() |
| Токен окончания объекта | writer.endObject() | generator.writeEndObject() |
| Токен имени поля | writer.name(<имя>) | generator.writeFieldName(<имя>) |
| Токен строкового значения | writer.value(<строка>) | generator.writeStringField(<имя>, <строка>) |
Примеры:
String str = "{\"message\":\"Hi\",\"place\":{\"name\":\"World!\"}}";
InputStream in = new ByteArrayInputStream(str.getBytes(Charset.forName("UTF-8")));
JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
while (reader.hasNext()) { // обходим все токены
JsonToken jsonToken = reader.peek(); // получаем тип следующего токена
if(jsonToken == JsonToken.BEGIN_OBJECT) { // если начало объекта
reader.beginObject();
} else if(jsonToken == JsonToken.END_OBJECT) { // если конец объекта
reader.endObject();
} if(jsonToken == JsonToken.STRING) { // в случае если токен строковое знание - выводим на экран
System.out.print(reader.nextString() + " "); // напечатает Hi World!
} else {
reader.skipValue(); // пропускаем все прочие токены
}
}
reader.close();
OutputStream outputStream = new ByteArrayOutputStream();
JsonWriter writer = new JsonWriter(new OutputStreamWriter(outputStream, "UTF-8"));
writer.beginObject(); // создаем токен начала главного объекта
writer.name("message"); // записываем поле message
writer.value("Hi");
writer.name("place"); // сохраняем объект Place в поле place
writer.beginObject(); // начинаем объект Place
writer.name("name");
writer.value("World!");
writer.endObject(); // закрываем объект Place
writer.endObject(); // закрываем главный объект
writer.close();
System.out.println(outputStream.toString()); // напечатает "{"message":"Hi","place":{"name":"World!"}}"
JsonFactory jsonFactory = new JsonFactory();
JsonParser jsonParser = jsonFactory.createParser("{\"message\":\"Hi\",\"place\":{\"name\":\"World!\"}}");
JsonToken jsonToken = jsonParser.nextToken();
while(jsonParser.hasCurrentToken()) { // обходим токены
if(jsonToken == VALUE_STRING) { // в случае если токен строковое знание - выводим на экран
System.out.print(jsonParser.getText() + " "); // напечатает "Hi World!"
}
jsonToken = jsonParser.nextToken();
}
JsonFactory jsonFactory = new JsonFactory();
OutputStream outputStream = new ByteArrayOutputStream();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(outputStream, JsonEncoding.UTF8);
jsonGenerator.writeStartObject(); // создаем токен начала главного объекта
jsonGenerator.writeStringField("message", "Hi"); // создаем поле message
jsonGenerator.writeFieldName("place");
jsonGenerator.writeStartObject(); // начинаем объект Place
jsonGenerator.writeStringField("name", "World!");
jsonGenerator.writeEndObject(); // закрываем объект Place
jsonGenerator.writeEndObject(); // закрываем главный объект
jsonGenerator.close();
System.out.println(outputStream.toString()); // напечатает "{"message":"Hi","place":{"name":"World!"}}"
Методы:
| Действие | JsonPath | FastJson | Jackson |
|---|---|---|---|
| Type | XPath-like | XPath-like | JSON Pointer-like |
| Получение значения по фильтру | JsonPath.read(<json>, <шаблон>) | JSONPath.eval(<java_объект>, <шаблон>) | mapper.readTree(<json>).at(<template>) |
| Получение коллекции по фильтру | JsonPath.read(<json>, <шаблон>) | JSONPath.eval(<java_объект>, <шаблон>) | mapper.readTree(<json>).at(<template>) |
Давайте посмотрим примеры, будем использовать все тот же json
jsonString =
{
"message": "Hi",
"place": {
"name": "World"
}
}
String jsonHiWorld = "{\"message\":\"Hi\",\"place\":{\"name\":\"World!\"}}\"";
String message = JsonPath.read(jsonHiWorld, "$.message");
String place = JsonPath.read(jsonHiWorld, "$.place.name");
System.out.println(message + " " + place); // напечатает "Hi World!"
// преобразования из json'a в Java объекты
String jsonString = "{\"message\":\"Hi\",\"place\":{\"name\":\"World!\"}}\"";
Human newHuman = JSON.parseObject(jsonString, Human.class);
// поиск информации в Java объектах используя eval
Object message = JSONPath.eval(newHuman, "$.message");
Object world = JSONPath.eval(newHuman, "$.place.name");
System.out.println(message + " " + world); // print Hi World
List<String> authors = JsonPath.read(json, "$.store.book[*].author");
System.out.println("authors: " + authors); // print ["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]
List<Map<String, Object>> expensiveBooks = JsonPath
.using(Configuration.defaultConfiguration())
.parse(json)
.read("$.store.book[?(@.price > 22)].title", List.class);
System.out.println(expensiveBooks); // print ["Hello, Middle-earth! "]
где json это
String json = "{\n" +
" \"store\": {\n" +
" \"book\": [\n" +
" {\n" +
" \"category\": \"reference\",\n" +
" \"author\": \"Nigel Rees\",\n" +
" \"title\": \"Sayings of the Century\",\n" +
" \"price\": 8.95\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"Evelyn Waugh\",\n" +
" \"title\": \"Sword of Honour\",\n" +
" \"price\": 12.99\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"Herman Melville\",\n" +
" \"title\": \"Moby Dick\",\n" +
" \"isbn\": \"0-553-21311-3\",\n" +
" \"price\": 8.99\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"J. R. R. Tolkien\",\n" +
" \"title\": \"Hello, Middle-earth! \",\n" +
" \"isbn\": \"0-395-19395-8\",\n" +
" \"price\": 22.99\n" +
" }\n" +
" ],\n" +
" \"bicycle\": {\n" +
" \"color\": \"red\",\n" +
" \"price\": 19.95\n" +
" }\n" +
" },\n" +
" \"expensive\": 10\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
String jsonHiWorld = "{\"message\":\"Hi\",\"place\":{\"name\":\"World!\"}}\"";
String message = mapper.readTree(jsonHiWorld).at("/message").asText();
String place = mapper.readTree(jsonHiWorld).at("/place/name").asText();
System.out.println(message + " " + place); // print "Hi World!"
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
String author = root.at("/store/book/3/title").asText();
System.out.println(author); // print ["Hello, Middle-earth! "]
...
private final static String json = "{\n" +
" \"store\": {\n" +
" \"book\": [\n" +
" {\n" +
" \"category\": \"reference\",\n" +
" \"author\": \"Nigel Rees\",\n" +
" \"title\": \"Sayings of the Century\",\n" +
" \"price\": 8.95\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"Evelyn Waugh\",\n" +
" \"title\": \"Sword of Honour\",\n" +
" \"price\": 12.99\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"Herman Melville\",\n" +
" \"title\": \"Moby Dick\",\n" +
" \"isbn\": \"0-553-21311-3\",\n" +
" \"price\": 8.99\n" +
" },\n" +
" {\n" +
" \"category\": \"fiction\",\n" +
" \"author\": \"J. R. R. Tolkien\",\n" +
" \"title\": \"Hello, Middle-earth! \",\n" +
" \"isbn\": \"0-395-19395-8\",\n" +
" \"price\": 22.99\n" +
" }\n" +
" ],\n" +
" \"bicycle\": {\n" +
" \"color\": \"red\",\n" +
" \"price\": 19.95\n" +
" }\n" +
" },\n" +
" \"expensive\": 10\n" +
"}";
Осталось рассмотреть вопросы генерации Java классов и валидации json. Советую посмотреть следующие два online ресурса:
Давайте рассмотрим варианты использование этих библиотек в Java коде.
// Init json
String source = "{\n" +
" \"type\":\"object\",\n" +
" \"properties\": {\n" +
" \"messageHiWorld\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"bar\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"baz\": {\n" +
" \"type\": \"boolean\"\n" +
" }\n" +
" }\n" +
"}";
// Init config
JCodeModel codeModel = new JCodeModel();
GenerationConfig config = new DefaultGenerationConfig() {
@Override
public boolean isGenerateBuilders() { // set config option by overriding method
return true;
}
};
// Generate Java POJO from json
SchemaMapper mapper = new SchemaMapper(new RuleFactory(config, new Jackson2Annotator(), new SchemaStore()), new SchemaGenerator());
mapper.generate(codeModel, "HelloWorldClass", "com.github.vedenin", source);
// Save generated class to file
File directory = new File("helloworlds/3.8-json/jsonschema2pojo/output");
directory.mkdirs();
codeModel.build(directory);
// Show generated class
File cls = new File("helloworlds/3.8-json/jsonschema2pojo/output/com/github/vedenin/HelloWorldClass.java");
String codeHelloWorld = Files.toString(cls, Charsets.UTF_8);
System.out.println(codeHelloWorld);
final JsonNode fstabSchema = Utils.loadResource("/fstab.json");
final JsonNode good = Utils.loadResource("/fstab-good.json");
final JsonNode bad = Utils.loadResource("/fstab-bad.json");
final JsonNode bad2 = Utils.loadResource("/fstab-bad2.json");
final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
final JsonSchema schema = factory.getJsonSchema(fstabSchema);
ProcessingReport report;
report = schema.validate(good);
System.out.println(report);
report = schema.validate(bad);
System.out.println(report);
report = schema.validate(bad2);
System.out.println(report);
<build>
<plugins>
<plugin>
<groupId>org.jsonschema2pojo</groupId>
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
<version>0.4.22</version>
<configuration>
<sourceDirectory>${basedir}/src/main/resources</sourceDirectory>
<targetPackage>com.github.vedenin</targetPackage>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
b) Положить нужные схемы json в sourceDirectory c) После запуска install maven'a по всем схемам будут сгенерированы Java классы.
Документация всех библиотек:
Все примеры: