Чтение и запись JSON на Java
Обозначение объектов JavaScript или, короче говоря, JSON -это формат обмена данными, который был введен в 1999 году и получил широкое распространение в середине 2000-х годов. В настоящее время это де-факто стандартный формат для общения между веб-сервисами и их клиентами (браузерами, мобильными приложениями и т.д.). Знание того, как читать и писать, является необходимым навыком для любого разработчика программного обеспечения.
Несмотря на то, что ДЖЕЙСОН был получен из JavaScript, это независимый от платформы формат. Вы можете работать с ним на нескольких языках программирования, включая Java, Python, Ruby и многие другие. Действительно, любой язык, который может анализировать строку, может обрабатывать JSON.
Популярность JSON привела к его встроенной поддержке многими базами данных, последние версии PostgreSQL и MySQL содержат встроенную поддержку запроса данных, хранящихся в полях JSON. Базы данных NoSQL, такие как MongoDB , были построены на основе этого формата и используют документы JSON для хранения записей, точно так же, как таблицы и строки хранят записи в реляционной базе данных.
Одним из основных преимуществ JSON по сравнению с форматом данных XML является размер документа. Поскольку JSON не имеет схем, нет необходимости переносить огромные структурные накладные расходы, такие как пространства имен и оболочки.
JSON-это универсальный формат данных, который имеет шесть типов данных:
Давайте взглянем на простой документ JSON:
Эта структура определяет объект, представляющий человека по имени “Бенджамин Уотсон”. Здесь мы можем ознакомиться с его подробностями, такими как его возраст, семейное положение и хобби.
По сути, объект JSON – это не более чем строка. Строка, представляющая объект, поэтому объекты JSON часто называются Строками JSON или документами JSON .
json-просто
Поскольку в Java нет встроенной поддержки JSON, прежде всего, мы должны добавить новую зависимость, которая обеспечивала бы ее для нас. Для начала мы будем использовать модуль json-simple , добавив его в качестве зависимости Maven.
com.googlecode.json-simple json-simple
Этот модуль полностью соответствует спецификации JSON RFC4627 и обеспечивает основные функциональные возможности, такие как кодирование и декодирование объектов JSON, и не имеет никаких зависимостей от внешних модулей.
Давайте создадим простой метод, который будет принимать имя файла в качестве параметра и записывать некоторые жестко закодированные данные JSON:
public static void writeJsonSimpleDemo(String filename) throws Exception
Здесь мы создаем экземпляр класса JSONObject , указывая имя и возраст в качестве свойств. Затем мы создаем экземпляр класса JSONArray , добавляя два строковых элемента и помещая его в качестве третьего свойства нашего sampleObject . В конечном счете, мы преобразуем образец объекта в документ JSON, вызывающий метод toJSONString() и записываем его в файл.
Чтобы запустить этот код, мы должны создать точку входа в наше приложение, которая могла бы выглядеть следующим образом:
В результате выполнения этого кода мы получим файл с именем example.json в корне нашего пакета. Содержимое файла будет документом JSON со всеми свойствами, которые мы ввели:
Отлично! У нас только что был наш первый опыт работы с форматом JSON, и мы успешно сериализовали в него объект Java и записали его в файл.
Теперь, с небольшим изменением нашего исходного кода, мы можем прочитать объект JSON из файла и распечатать его на консоли либо полностью, либо распечатать выбранные отдельные свойства:
public static void main(String[] args) throws Exception < JSONObject jsonObject = (JSONObject) readJsonSimpleDemo("example.json"); System.out.println(jsonObject); System.out.println(jsonObject.get("age")); >public static Object readJsonSimpleDemo(String filename) throws Exception
Важно отметить, что метод parse() возвращает Объект , и мы должны явно привести его к JSONObject .
Если у вас неправильный или поврежденный документ JSON, вы получите исключение, подобное этому:
Exception in thread "main" Unexpected token END OF FILE at position 64.
Чтобы смоделировать это, попробуйте удалить последнюю закрывающую скобку > .
Копать Глубже
Несмотря на то, что json-простой полезен, он не позволяет нам использовать пользовательские классы без написания дополнительного кода. Давайте предположим, что у нас есть класс, представляющий человека из нашего первоначального примера:
kids) < this.name = name; this.age = age; this.isMarried = isMarried; this.hobbies = hobbies; this.kids = kids; >Person(String name, int age) < this(name, age, false, null, null); >private String name; private Integer age; private Boolean isMarried; private List hobbies; private List
kids; // getters and setters @Override public String toString()
Нашей задачей было бы десериализовать этот объект из файла в экземпляр класса Person . Давайте сначала попробуем сделать это с помощью simple-json .
Изменив наш метод main () , повторно используя статический readSimpleJsonDemo() и добавив необходимый импорт, мы доберемся до:
public static void main(String[] args) throws Exception < JSONObject jsonObject = (JSONObject) readJsonSimpleDemo("example.json"); Person ben = new Person( (String) jsonObject.get("name"), Integer.valueOf(jsonObject.get("age").toString()), (Boolean) jsonObject.get("isMarried"), (List) jsonObject.get("hobbies"), (List ) jsonObject.get("kids")); System.out.println(ben); >
Это выглядит не очень хорошо, у нас много странных типов, но, похоже, это делает свою работу, верно?
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
Давайте попробуем распечатать на консоли массив дети нашего Человека , а затем возраст первого ребенка.
System.out.println(ben.getKids()); System.out.println(ben.getKids().get(0).getAge());
Как мы видим, первый вывод консоли показывает, казалось бы, хороший результат:
но второй выдает Исключение :
Exception in thread "main" java.lang.ClassCastException: org.json.simple.JSONObject cannot be cast to com.stackabuse.json.Person
Проблема здесь в том, что наш тип в Список не создал два новых Человека объекта, он просто вставил все, что там было – JSONObject в нашем текущем случае. Когда мы попытались копнуть глубже и узнать фактический возраст первого ребенка, мы столкнулись с ClassCastException .
Это большая проблема, которую, я уверен, вы сможете преодолеть, написав кучу очень умного кода, которым вы могли бы гордиться, но есть простой способ сделать это с самого начала.
Джексон
Библиотека, которая позволит нам делать все это очень эффективно, называется Джексон . Это очень распространено и используется в крупных корпоративных проектах, таких как Спящий режим .
Давайте добавим его в качестве новой зависимости Maven:
com.fasterxml.jackson.core jackson-databind
Основной класс , который мы будем использовать, называется ObjectMapper , у него есть метод readValue () , который принимает два аргумента: источник для чтения и класс для приведения результата.
ObjectMapper может быть настроен с несколькими различными параметрами, переданными в конструктор:
FAIL_ON_SELF_РЕФЕРЕНЦИИ | Функция, которая определяет, что происходит, когда прямая самоссылка обнаруживается POJO (и для нее не включена обработка идентификатора объекта): либо создается исключение JsonMappingException (если это правда), либо ссылка обычно обрабатывается (ложь). |
ОТСТУП_ВЫХОД | Функция, которая позволяет включать (или отключать) отступы для базового генератора, используя принтер pretty по умолчанию, настроенный для ObjectMapper (и авторов объектов, созданных из mapper). |
ORDER_MAP_ENTRIES_BY_KEYES | Функция, определяющая, будут ли записи карты сначала отсортированы по ключу перед сериализацией или нет: если включено, при необходимости выполняется дополнительный шаг сортировки (не требуется для отсортированных карт), если отключено, дополнительная сортировка не требуется. |
USE_EQUALITY_FOR_OBJECT_ID | Функция, определяющая, сравнивается ли идентичность объекта с использованием истинной идентичности объекта на уровне JVM (ложь); или метод equals (). |
Функция, определяющая, как сериализуется тип char []: при включении сериализуется как явный массив JSON (с односимвольными строками в качестве значений); при отключении по умолчанию сериализуется как строки (что более компактно). | |
МЕТКИ ВРЕМЕНИ WRITE_DATE_KEYS_AS_TIMESTAMPS | Функция, определяющая, будут ли даты (и подтипы), используемые в качестве ключей карты, сериализованы как метки времени или нет (если нет, будут сериализованы как текстовые значения). |
WRITE_DATE_TIMESTAMPS_АС_НАНОСЕКУНДЫ | Функция, которая определяет, следует ли записывать числовые значения временных меток с использованием наносекундных временных меток (включено) или нет (отключено); если и только если тип данных поддерживает такое разрешение. |
МЕТКИ ВРЕМЕНИ WRITE_DATES_AS_TIMESTAMPS | Функция, которая определяет, следует ли сериализовать значения даты (и даты/времени) (и такие вещи, основанные на данных, как календари), как числовые метки времени (true; по умолчанию) или как что-то другое (обычно текстовое представление). |
WRITE_DATES_С_ЗОНОМ_ИД | Функция, которая определяет, следует ли сериализовать значения даты/времени так, чтобы они включали идентификатор часового пояса, в тех случаях, когда сам тип содержит информацию о часовом поясе. |
Полный список сериализации перечисления доступен здесь .
public static void main(String[] args) throws Exception
К сожалению, после запуска этого фрагмента кода мы получим исключение:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.stackabuse.json.Person]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
Судя по всему, мы должны добавить конструктор по умолчанию в класс Person :
При повторном запуске кода мы увидим еще одно исключение:
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "isMarried" (class com.stackabuse.json.Person), not marked as ignorable (5 known properties: "hobbies", "name", "married", "kids", "age"])
Этот вопрос немного сложнее решить, так как сообщение об ошибке не говорит нам, что делать для достижения желаемого результата. Игнорирование свойства не является жизнеспособным вариантом, поскольку мы явно указываем его в документе JSON и хотим, чтобы он был переведен в результирующий объект Java.
Проблема здесь связана с внутренней структурой библиотеки Джексона. Он выводит имена свойств из геттеров, удаляя их первые части. В случае getafe() и getName() это работает отлично, но с женат() это не так, и предполагается, что поле должно называться женат вместо Женат .
Грубый, но рабочий вариант – мы можем решить эту проблему, просто переименовав получателя в is Женат . Давайте продолжим и попробуем это сделать.
Больше никаких исключений не появляется, и мы видим желаемый результат!
Person, Person]> [Person, Person] 5
Хотя результат удовлетворяет, есть лучший способ обойти это, чем добавлять еще один is к каждому из ваших логических геттеров.
Мы можем достичь того же результата, добавив аннотацию к методу женат() :
Таким образом, мы явно сообщаем Джексону название поля, и ему не нужно угадывать. Это может быть особенно полезно в тех случаях, когда поле называется совершенно иначе, чем геттеры.
Вывод
JSON-это легкий текстовый формат, который позволяет нам представлять объекты и передавать их через Интернет или хранить в базе данных.
В Java отсутствует встроенная поддержка манипуляций с JSON, однако существует несколько модулей, обеспечивающих эту функциональность. В этом уроке мы рассмотрели модули json-simple и Jackson , показав сильные и слабые стороны каждого из них.
Работая с JSON, вы должны иметь в виду нюансы модулей, с которыми вы работаете, и тщательно отлаживать исключения, которые могут появляться.