JavaRush /Java блог /Random /Json схема: зачем и кому она нужна
Сергеев Виктор
40 уровень

Json схема: зачем и кому она нужна

Статья из группы Random
Привет, странник. Сегодня я хочу поведать тебе о небольшой магии. Ты наверно уже слышал про json. Это такой универсальный язык: его понимает машина и легко читает человек. Вот типичный пример json сообщения:

{  
   "помещение":{  
      "название":"избушка",
      "разумна":true
   },
   "основание":{  
      "тип":"курьи ноги",
      "количество":2
   },
   "проживающие":[  
      {  
         "имя":"Баба Яга",
         "профиль":"ведьма"
      }
   ],
   "местоположение":{  
      "адрес":"граница леса"
   }
}
Удобно ведь так общаться, правда? Если до этого ты не знал, что такое json, теперь знаешь. Как это использовать в java коде? Json стал универсальным форматом. Расшифровывается он как JavaScript Object Notation, но уже давно вышел за пределы javascript и используется почти везде. В java есть несколько библиотек, упрощающих работу с json. Вот самые известные: Я буду использовать вторую. Там их 2 версии codehaus и fasterxml, отличий в них не замечал, так что тут можете использовать любую. Вот такой кусок кода:

ObjectMapper mapper = new ObjectMapper();
return mapper.readValue("сюда json", "сюда класс");
поможет перевести json в объект. И мы приближаемся к самому главному. Написанию класса для этого json. Можно это делать руками, создать структуру примерно такую:

-----------------------------------com.fairytale.Base.java-----------------------------------

package com.fairytale;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;


@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"type",
"quantity"
})
public class Base {

@JsonProperty("type")
public String type = "";
@JsonProperty("quantity")
public int quantity = 0;

}
-----------------------------------com.fairytale.Hut.java-----------------------------------

package com.fairytale;

import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;


@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"room",
"base",
"residents",
"location"
})
public class Hut {

@JsonProperty("room")
public Room room;
@JsonProperty("base")
public Base base;
@JsonProperty("residents")
public List<Resident> residents = new ArrayList<Resident>();
@JsonProperty("location")
public Location location;

}
-----------------------------------com.fairytale.Location.java-----------------------------------

package com.fairytale;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;


@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"address"
})
public class Location {

@JsonProperty("address")
public String address = "";

}
-----------------------------------com.fairytale.Resident.java-----------------------------------

package com.fairytale;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;


@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"name",
"profile"
})
public class Resident {

@JsonProperty("name")
public String name = "";
@JsonProperty("profile")
public String profile = "";

}
-----------------------------------com.fairytale.Room.java-----------------------------------

package com.fairytale;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"name",
"reasonable"
})
public class Room {

@JsonProperty("name")
public String name = "";
@JsonProperty("reasonable")
public boolean reasonable = false;

}
Я специально опустил геттеры, сеттеры, конструкторы и прочие атрибуты pojo, иначе ты устал бы проматывать =) А теперь посмотри сюда:

{
  "definitions": {},
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://example.com/root.json",
  "type": "object",
  "title": "The Root Schema",
  "required": [
    "room",
    "base",
    "residents",
    "location"
  ],
  "properties": {
    "room": {
      "$id": "#/properties/room",
      "type": "object",
      "title": "The Room Schema",
      "required": [
        "name",
        "reasonable"
      ],
      "properties": {
        "name": {
          "$id": "#/properties/room/properties/name",
          "type": "string",
          "title": "The Name Schema",
          "default": "",
          "examples": [
            "избушка"
          ],
          "pattern": "^(.*)$"
        },
        "reasonable": {
          "$id": "#/properties/room/properties/reasonable",
          "type": "boolean",
          "title": "The Reasonable Schema",
          "default": false,
          "examples": [
            true
          ]
        }
      },
	"additionalProperties": false
    },
    "base": {
      "$id": "#/properties/base",
      "type": "object",
      "title": "The Base Schema",
      "required": [
        "type",
        "quantity"
      ],
      "properties": {
        "type": {
          "$id": "#/properties/base/properties/type",
          "type": "string",
          "title": "The Type Schema",
          "default": "",
          "examples": [
            "курьи ноги"
          ],
          "pattern": "^(.*)$"
        },
        "quantity": {
          "$id": "#/properties/base/properties/quantity",
          "type": "integer",
          "title": "The Quantity Schema",
          "default": 0,
          "examples": [
            2
          ]
        }
      },
	"additionalProperties": false
    },
    "residents": {
      "$id": "#/properties/residents",
      "type": "array",
      "title": "The Residents Schema",
      "items": {
        "$id": "#/properties/residents/items",
        "type": "object",
        "title": "The Items Schema",
        "required": [
          "name",
          "profile"
        ],
        "properties": {
          "name": {
            "$id": "#/properties/residents/items/properties/name",
            "type": "string",
            "title": "The Name Schema",
            "default": "",
            "examples": [
              "Баба Яга"
            ],
            "pattern": "^(.*)$"
          },
          "profile": {
            "$id": "#/properties/residents/items/properties/profile",
            "type": "string",
            "title": "The Profile Schema",
            "default": "",
            "examples": [
              "ведьма"
            ],
            "pattern": "^(.*)$"
          }
        },
	    "additionalProperties": false
      }
    },
    "location": {
      "$id": "#/properties/location",
      "type": "object",
      "title": "The Location Schema",
      "required": [
        "address"
      ],
      "properties": {
        "address": {
          "$id": "#/properties/location/properties/address",
          "type": "string",
          "title": "The Address Schema",
          "default": "",
          "examples": [
            "граница леса"
          ],
          "pattern": "^(.*)$",
		  "additionalProperties": false
        }
      },
	"additionalProperties": false
    }
  },
	"additionalProperties": false
}
Это json схема структуры выше. Теперь пора объяснить, зачем она тебе. Она избавит от необходимости писать классы и поддерживать их. Есть такой хороший проект jsonschema2pojo. Он предлагает плагины для сборщиков проекта (мавен, грейдл), которые напишут эти классы для тебя во время сборки. Приведу пример из моего проекта:

<plugin>
    <groupId>org.jsonschema2pojo</groupId>
    <artifactId>jsonschema2pojo-maven-plugin</artifactId>
    <version>0.4.37</version>

    <executions>
        <execution>
            <id>jsonschema2opjo</id>
            <configuration>
                <sourceDirectory>${project.basedir}/src/main/resources/json-schema/</sourceDirectory>
                <targetPackage>tester.model</targetPackage>
                <outputDirectory>${project.basedir}/target/generated-sources/jsonschema/</outputDirectory>
                <useCommonsLang3>true</useCommonsLang3>
                <includeConstructors>true</includeConstructors>
                <generateBuilders>true</generateBuilders>
                <includeToString>true</includeToString>
                <usePrimitives>true</usePrimitives>
            </configuration>
            <goals>
                <goal>generate</goal>
            </goals>
            <phase>generate-sources</phase>
        </execution>
    </executions>
</plugin>
Это его настройка. Самое интересное тут:

<useCommonsLang3>true</useCommonsLang3>
<includeConstructors>true</includeConstructors>
<generateBuilders>true</generateBuilders>
<includeToString>true</includeToString>
<usePrimitives>true</usePrimitives>
Это указание, как писать класс: useCommonsLang3 – использовать библиотеку CommonsLang3 includeConstructors – напишет конструктор generateBuilders – встроит паттерн билдер includeToString – добавит toString usePrimitives – указание использовать примитивы Чем это лучше самописного кода?
  1. Вы можете кастомизировать классы одной строчкой. Например, надо добавить суффикс Pojo к каждому классу. Достаточно добавить <classNameSuffix>Pojo</classNameSuffix> собирать проект — и готово. Иначе пришлось бы менять имена каждому класс руками.

    Этих параметров очень много, обо всех стоит почитать в доках

  2. Если у твоего проекта есть потребитель, намного проще будет отдать ему json схемы, а не java классы. Как я уже говорил, схемы универсальны и потребитель просто сгенерит pojo на своем языке.

  3. Они гораздо меньше. Пример сверху содержит много не всегда нужной информации, скажем, паттерны и примеры. Но если вернуть их в java код, он тоже сильно вырастет. И не забудь про шаблонный код, который в схемах указывается парой настроек в плагине, а в коде его надо писать самому. И да, я знаю про ломбок, тут альтернатива.

  4. Никакой логики в pojo. Когда ваши классы самописные, кто-то может соблазниться и добавить удобный ему метод. В json схемы метод добавить нельзя, как и в сгенерированный класс.

Наверно, на этом все.

Вывод:

Я старался донести, что json схемы очень хороший формат взаимодействия между проектами. Однажды я с ним встретился на одном проекте, и он запал мне в сердце. Я использую их почти везде. Да, это не всегда удобно, ведь чтобы посмотреть исходники, надо собирать проект. Но это pojo, а значит, логики там быть не может, со схемами и не будет. PS.. я иногда объясняю плохо, так что вот видео с хорошим докладом: Кирилл Меркушев — Кодогенерация как способ решения проблем автоматизатора.
Комментарии (16)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Серега Батенин Уровень 34
14 ноября 2022
Ребят вопрос немного не по теме, но может кто нибудь видел классный сайт с генерацией джейсон файлов с рандомными данными(бесплатно до 1000 записей разрешают), там можно было еще задавать количество полей, их названия, типы. я 2 раза в каких то статьях натыкался, но по глупости не сохранил себе, думал легко найти смогу, но вот что то не получается. Буду очень признателен, если у кого то есть
shteynu Уровень 19
15 января 2019
Насколько я понимаю чтобы разджэйсонить сообщение, то есть создать dto классы надо иметь представление об объектах и полях этого json сообщения?
Taras Kutselya Уровень 24
18 октября 2018
Интересно... но для меня пока как то сложно и не совсем понятно применение. Не могу понять для автоматизации постройки каких типов классов его использовать.
Евгений Гродно Уровень 36 Expert
13 октября 2018
Отличная статья. Виктору огромное спасибо! И обязательно новую книгу ;)
Viacheslav Уровень 3
11 октября 2018
Спасибо за материал. Хочу дополнить ссылкой на материал (было бы здорово вставить куда-нибудь, наверно): Кирилл Меркушев — Кодогенерация как способ решения проблем автоматизатора.
Стас Пасинков Уровень 26 Master
10 октября 2018
впервые о таком читаю, спасибо :) но лично мне схема выглядит намного сложнее самого класса. да и в примере код с классами получился на 15 строк короче самой схемы)) в общем, со своими классами (+ломбок) - как-то "понятнее" и привычнее)) но за альтернативу спасибо :)
Nikita Koliadin Уровень 40
10 октября 2018
Магия :) Люблю магию. Пойду колдовать! Спасибо автору!)))