JavaRush/Java блог/Random/Json схема: зачем и кому она нужна

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, 19:56
Ребят вопрос немного не по теме, но может кто нибудь видел классный сайт с генерацией джейсон файлов с рандомными данными(бесплатно до 1000 записей разрешают), там можно было еще задавать количество полей, их названия, типы. я 2 раза в каких то статьях натыкался, но по глупости не сохранил себе, думал легко найти смогу, но вот что то не получается. Буду очень признателен, если у кого то есть
shteynu
Уровень 19
15 января 2019, 18:56
Насколько я понимаю чтобы разджэйсонить сообщение, то есть создать dto классы надо иметь представление об объектах и полях этого json сообщения?
15 января 2019, 19:09
Да. При этом за счет аннотаций можно задавать несовпадающие поля (если в DTO одно название, а в json другое), задавать формат даты, что сериализовать/десерилиазовать и т.д. По сути очень похоже на xml и его xsd схему.
Taras Kutselya
Уровень 24
18 октября 2018, 08:45
Интересно... но для меня пока как то сложно и не совсем понятно применение. Не могу понять для автоматизации постройки каких типов классов его использовать.
Сергеев Виктор
Уровень 40
Master
19 октября 2018, 21:46
для классов дто (data transfer object) часто это объекты, которые просто переносят данные. Буду брать пример из жизни. У нас они используются как объекты в которые будут записаны данные приходящие со стороны клиента (браузера). Вот пишу я сейчас этот комментарий и когда нажму отправить на сервер jr полетит json возможно такого вида: { "type": "comment", "author": "Сергеев Виктор", "text": "тут текст комментария" } И такую запись хорошо бы перевести в java объект. Для этого есть специальные библиотеки и фреймворки и думать об этом особо не надо. Мы пишем код исходя из того, что данный json будет у нас в виде java объекта. Такой объект не обладает логикой (методами со сложной логикой). Это POJO объекты (поля, конструкторы, геттеры, сеттеры, equals, hashcode, toString). Нет смысла писать их самому. Особенно если есть соседний проект, кому нужны схемы =)
Taras Kutselya
Уровень 24
20 октября 2018, 03:02
Логично, спасибо за пояснение.
Евгений Гродно
Уровень 36
Expert
13 октября 2018, 00:59
Отличная статья. Виктору огромное спасибо! И обязательно новую книгу ;)
Viacheslav
Уровень 3
11 октября 2018, 09:16
Спасибо за материал. Хочу дополнить ссылкой на материал (было бы здорово вставить куда-нибудь, наверно): Кирилл Меркушев — Кодогенерация как способ решения проблем автоматизатора.
Сергеев Виктор
Уровень 40
Master
11 октября 2018, 19:29
круть, добавлю в статью =)
Стас Пасинков Software Developer в Zipy Master
10 октября 2018, 17:40
впервые о таком читаю, спасибо :) но лично мне схема выглядит намного сложнее самого класса. да и в примере код с классами получился на 15 строк короче самой схемы)) в общем, со своими классами (+ломбок) - как-то "понятнее" и привычнее)) но за альтернативу спасибо :)
Сергеев Виктор
Уровень 40
Master
10 октября 2018, 18:01
в данном случае согласен, но есть несколько причин "живых". Из сегодняшнего, людям понадобилось добавить Serializable ко всем дто. Со схемами, это одна строка в помнике, в классах это около 30 классов надо поправить. Про длинну классов, там убраны все атрибуты pojo, но их может заменить ломбок, билдер тоже от ломбока, валидацию (jsr 303) придется писать ручками. Ну и зачем они были нужным нам, передать другому проекту для валидации запросов-ответов, java классы тут не помогли бы =)
Стас Пасинков Software Developer в Zipy Master
11 октября 2018, 16:12
ну да, я согласен, что в каких-то случаях может очень даже пригодиться!) поэтому и рад, что узнал о таком :) а вы что, между проектами обменивались java-объектами напрямую? или я что-то не так понял?
Сергеев Виктор
Уровень 40
Master
11 октября 2018, 19:29
ответы в виде json =) А если кто-то обращается к твоему проекту, ему проще отдать схемы и тогда ему не придется самому создавать классы =) Из рабочих примеров, есть другой проект, который обеспечивает нам связь внутренней сети и внешней. Но передать через него можно только заранее обговоренные json сообщения, как ты догадался, мы отдаем ему схемы наших сообщений, чтобы он их пропускал =)
15 января 2019, 19:11
У нас есть смежные проекты/системы, к которым нужно обращаться. Они вообще написаны на C# и PHP. За счет json нас это мало волнует - дернули веб-сервис, получили объект, привели к POJO. Аналогично для них выставляли сервисы.
Nikita Koliadin Full Stack Developer в Приватбанк
10 октября 2018, 13:49
Магия :) Люблю магию. Пойду колдовать! Спасибо автору!)))
Евгений Гродно
Уровень 36
Expert
13 октября 2018, 01:00
Я тоже полюбил json, с ним жить гораздо проще))