Добро пожаловать в краткое руководство по различным вариантам использования JSON для Java в Spring Boot. Если у вас есть существующее приложение Spring Boot, вы можете добавить в него перечисленные ниже классы и следовать им. Если у вас нет существующего приложения Spring Boot, подумайте о том, чтобы сначала следовать руководству Spring Создание веб-службы RESTful , чтобы добраться до места, где вы можете поэкспериментировать со следующим.
Возможно, наиболее распространенным вариантом использования является разрешение кому-либо отправлять HTTP POST-запрос на конечную точку и автоматическое преобразование отправленного JSON в обычный старый Java-объект (POJO). Вот пример, который “просто работает” с Spring Boot и Jackson.
JSON для этого примера является:
{ "brand": "Apple", "model": "iPhone" }
Соответствующий объект Java будет:
И соответствующий контроллер:
Вы можете использовать такой инструмент, как Postman или curl , для публикации JSON. Убедитесь, что вы отправили заголовок “Content-Type” со значением “application/json”, чтобы Spring Boot автоматически проанализировал его в экземпляре Smartphone. Вот как это будет выглядеть как команда curl:
curl --location --request POST 'localhost:8080/smartphone' \ --header 'Content-Type: application/json' \ --data-raw '{ "brand": "Apple", "model": "iPhone" }'
Как только этот запрос отправлен, приложение должно предсказуемо направить запрос к контроллеру, сопоставить запрос с объектом Java, проанализировать его, а затем записать следующее:
Received new smartphone: Apple iPhone
Тот же вариант использования, что и выше, может быть достигнут с помощью неизменяемого объекта. Неизменяемые объекты лучше подходят для использования в многопоточном коде, и у них также есть преимущество в продвижении более поддерживаемого кода в целом.
Вот неизменяемая версия вышеупомянутого класса смартфонов:
Обратите внимание, что единственные различия между изменяемой и неизменяемой версией смартфона.java заключаются в следующем:
Переменные-члены объявляются окончательными.
Здесь нет сеттеров.
Конструктор снабжен аннотацией “@JsonCreator”.
Как вы можете видеть, легко создать неизменяемый класс для представления вашего JSON.
Сменив свой смартфон.класс java для этой новой версии, но с сохранением существующего SmartphoneController.java класс, вы должны иметь возможность сделать тот же запрос POST, что и раньше, и получить тот же результат.
Из-за их преимуществ я буду использовать неизменяемые объекты на протяжении всей оставшейся части этой статьи.
Иногда внутри объекта есть другой объект. Обработка содержащегося объекта как статического внутреннего класса POJO тесно моделирует эту взаимосвязь между объектами.
Подумайте, была ли в примере смартфона “модель”, которая не была одной строкой, а вместо этого была другим объектом с именем модели и/или версией модели:
{ "brand": "Apple", "model": { "name": "iPhone", "version": "11 Pro" } }
Обычно “Модель” представляют как статический внутренний класс. Никаких других изменений в том, как вы относитесь к этому классу, не требуется. Внутренний класс может быть представлен либо как неизменяемый, либо как изменяемый объект. Вот неизменяемая версия смартфона с неизменяемым статическим внутренним классом для представления модели:
Если вы используете эту версию смартфона.java с вашим сервисом, убедитесь и обновите информацию, которая SmartphoneController.java регистрирует, чтобы вы могли видеть все передаваемые данные. Вот пример curl, который РАЗМЕСТИТ вышеупомянутый объект:
curl --location --request POST 'localhost:8080/smartphone' \ --header 'Content-Type: application/json' \ --data-raw '{ "brand": "Apple", "model": { "name": "iPhone", "version": "11 Pro" } }'
Что, если кто-то добавит атрибут “features” к этому JSON, который представляет собой массив строк, описывающих различные функции каждого телефона? Как этот пример:
{ "brand": "Apple", "model": { "name": "iPhone", "version": "11 Pro" }, "features": ["Super Retina XDR display", "12 MP camera", "Up to 512 GB capacity"] }
Джексон может разобрать массив JSON либо в массив, либо в коллекцию, например в список. Мы можем взять ваш существующий смартфон.java из предыдущего примера и добавьте новый элемент функций с типом List
.
Никаких изменений в контроллер вносить вообще не нужно, но чтобы помочь вам визуализировать результат, попробуйте добавить оператор log в метод addSmartPhone, чтобы регистрировать полученные функции, например, строка 4 здесь:
Имея этот код на месте, перекомпилируйте и запустите приложение. Когда вы отправите приведенный выше JSON в конечную точку/smartphone, приложение войдет в систему:
Received new smartphone: Apple iPhone 11 Pro The features of the smartphone are Super Retina XDR display, 12 MP camera, Up to 512 GB capacity
Иногда размещенный JSON-файл представляет собой не объект, а массив. Допустим, нам нужна новая конечная точка в нашем сервисе, которая может принимать несколько смартфонов одновременно. JSON будет выглядеть следующим образом:
[{ "brand": "Apple", "model": { "name": "iPhone", "version": "11 Pro" }, "features": ["Super Retina XDR display", "12 MP camera", "Up to 512 GB capacity"] }, { "brand": "Samsung", "model": { "name": "Galaxy", "version": "S20" }, "features": ["6.2\" screen", "64MP camera", "4000 mAh battery"] }]
Нам нужно добавить новый метод к нашему контроллеру смартфона, чтобы получить список телефонов. Поскольку оба теперь будут совместно использовать возможность протоколирования полученного объекта, мы преобразуем его в частный метод. Теперь у нас есть это:
Наш POJO в смартфоне.java вообще не меняется.
Главное, что следует отметить в новом методе “addSmartphones”, – это то, что соответствующие типы изменились:
Параметр метода “смартфоны” имеет тип List
Возвращаемый тип –
ResponseEntity<Список<Смартфон>>
.Когда вы отправляете JSON, указанный выше, в эту конечную точку (убедитесь, что вы отправляете в/smartphones, а не только в/smartphone) затем вы увидите ожидаемое ведение журнала:
Received new smartphone: Apple iPhone 11 Pro The features of the smartphone are Super Retina XDR display, 12 MP camera, Up to 512 GB capacity Received new smartphone: Samsung Galaxy S20 The features of the smartphone are 6.2" screen, 64MP camera, 4000 mAh battery
Иногда объект JSON завернут в тонкую “оболочку” объекта JSON. Взяв наш исходный объект смартфона, обернуть его означало бы сделать сам этот объект атрибутом внешнего объекта, подобного этому:
{ "smartphone": { "brand": "Apple", "model": { "name": "iPhone", "version": "11 Pro" }, "features": [ "Super Retina XDR display", "12 MP camera", "Up to 512 GB capacity" ] } }
Очевидный способ обработки JSON подобным образом – создать другой класс, SmartphoneWrapper.java , который представляет собой оболочку:
Вы могли бы даже сделать смартфон статическим внутренним классом этой оболочки.
В этом подходе есть небольшая проблема, которая заключается в том, что весь результирующий код теперь усложняется тем, что он “проникает” через оболочку к смартфону внутри. Например, метод Smartphone Controller.addSmartphone может в конечном итоге выглядеть следующим образом:
С точки зрения контроллера, нас не волнует эта оболочка. Возможно, мы захотим выбросить его, когда получим подобный JSON, и продолжать работать только с объектом внутри.
Джексон предлагает функцию для обработки этого, называемую переносом корневых элементов. К сожалению, насколько мне известно, его можно включить или выключить только на уровне приложения. Итак, сначала вы можете добавить следующие настройки в application.properties вашего приложения Spring Boot:
spring.jackson.serialization.wrap-root-value=true spring.jackson.deserialization.unwrap-root-value=true
Теперь, когда эти настройки применены к приложению, нам нужно сообщить Джексону, что атрибут “смартфон” в нашем JSON содержит объект smartphone. Чтобы сделать это, мы переходим к смартфону.java и добавьте аннотацию “@JsonRootName(“смартфон”). ” Все остальное по-прежнему остается тем же самым в Smartphone.java:
Вам не нужно менять контроллер смартфона. Это все еще должно выглядеть так:
Попробуйте перестроить приложение прямо сейчас и протестировать его. Отправьте JSON из начала этого раздела в качестве тела вашего запроса. Все получается отлично!
Из-за этого изменения настроек и того факта, что оно происходит во всем приложении, любые другие тела запросов, отправляемые конечным точкам в других частях приложения, всегда должны быть обернуты. Если вы попытаетесь использовать POJO, подобный тому, который мы использовали в остальной части этой статьи, в другом контроллере, вы увидите исключение, подобное следующему:
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Root name 'brand' does not match expected ('smartphone') for type [simple type, class com.scottshipp.code.restservice.Smartphone]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'brand' does not match expected ('smartphone') for type [simple type, class com.scottshipp.code.restservice.Smartphone] at [Source: (PushbackInputStream); line: 2, column: 5] (through reference chain: com.scottshipp.code.restservice.Smartphone["brand"])]
По этой причине я предпочитаю не использовать корневую оболочку и просто мириться с проблемой “сквозного доступа” к пользовательскому объекту-оболочке. Ваш пробег может отличаться.
Я надеюсь, что это пошаговое руководство по общим вариантам использования Spring Boot JSON для синтаксического анализа было полезным. Я что-нибудь пропустил? Было ли что-нибудь неясно? Есть еще какие-нибудь комментарии или вопросы? Дайте мне знать в разделе комментариев ниже.
Оригинал: “https://dev.to/scottshipp/parsing-json-in-spring-boot-part-1-513”