openjdk.ruOpenJDK на русском

JEP 540: Simple JSON API (Incubator)

Простой JSON API, Incubator (инкубационный модуль)

AuthorsNaoto Sato, Paul Sandoz, Justin Lu, Stuart Marks
ОтветственныйNaoto Sato
ТипFeature
ОбластьJDK
СтатусIntegrated
Выпуск28
Компонентcore-libs
Обсуждениеcore dash libs dash dev at openjdk dot org
ТрудоёмкостьM
ДлительностьM
РецензентыAlex Buckley
ОдобренPaul Sandoz
Создан2024/11/13 23:54
Обновлён2026/09/09 17:03
Задача8344154

Аннотация

Определить простой стандартный API для разбора и генерации JSON-документов, чтобы для этого не требовалась внешняя библиотека. Дать возможность решать многие задачи обработки JSON при небольшом объёме кода. Это API в статусе Incubator.

История

Этот JEP заменяет JEP 198, Light-Weight JSON API, написанный в 2014 году. За прошедшие годы обстоятельства изменились, поэтому здесь мы выбираем другой подход.

Цели

  • Предоставить в платформе Java стандартное средство для обработки JSON-документов, соответствующих RFC 8259, без лишних формальностей.
  • Сохранить API небольшим, простым и лёгким в освоении. Предоставить только те типы данных и операции, которые нужны для строгого соответствия RFC 8259, чтобы упростить межмашинное взаимодействие. Избегать таких возможностей, как несколько конфигураций разбора, расширения синтаксиса, привязка данных и потоковая обработка.

  • Добиться того, чтобы код, который перемещается по JSON-документам с известной структурой и извлекает из них данные, был простым и читаемым. Поскольку у JSON-документов нет схем, такой код служит схемой de facto и должен читаться как схема.

  • Обеспечить простое и быстрое исследование незнакомых JSON-документов. Мы часто работаем с JSON-документами исследовательским способом: пишем код не по спецификации, а пробуя его на примерах документов. API должен предоставлять методы, которые быстро завершаются ошибкой с понятными сообщениями, чтобы исследование шло быстро.

  • Обеспечить устойчивую обработку отсутствующих или неожиданных значений, поскольку структура JSON-документов может со временем меняться.

  • Дать самому JDK возможность разбирать и генерировать JSON-документы.

Что не является целью

  • Целью не является создание API, который вытеснит устоявшиеся внешние библиотеки JSON.

Мотивация

JSON повсеместно используется в современных вычислениях. В экосистеме Java есть множество устоявшихся библиотек JSON: Jackson, Gson, Jakarta JSON Processing и Binding, Fastjson 2 и другие. Эти библиотеки не только позволяют разбирать и генерировать JSON-документы, но и поддерживают расширенные варианты синтаксиса JSON, такие как JSON5, а также включают возможности более высокого уровня, например привязку данных, то есть преобразование Java-объектов в JSON и обратно с широкими возможностями настройки, и потоковую обработку на основе событий.

Однако часто нам нужно лишь выполнить простую задачу, например извлечь какие-то данные из JSON-документа. Код на Python или Go для таких задач прост; код на Java должен быть столь же простым.

Например, рассмотрим задачу вычисления среднего значения набора прогнозируемых температур в ответе U.S. National Weather Service REST API. Ответ представляет собой JSON-документ следующего вида:

{
  ...
  "properties": {
    ...
    "periods": [
      {
        "number": 1,
        "name": "Today",
        "startTime": "2026-04-22T06:00:00-04:00",
        "endTime": "2026-04-22T18:00:00-04:00",
        "isDaytime": true,
        "temperature": 54,
        "temperatureUnit": "F",
        ...
      },
      {
        "number": 2,
        "name": "Tonight",
        "startTime": "2026-04-22T18:00:00-04:00",
        "endTime": "2026-04-23T06:00:00-04:00",
        "isDaytime": false,
        "temperature": 48,
        "temperatureUnit": "F",
        ...
      },
      {
        "number": 3,
        "name": "Thursday",
        "startTime": "2026-04-23T06:00:00-04:00",
        "endTime": "2026-04-23T18:00:00-04:00",
        "isDaytime": true,
        "temperature": 68,
        "temperatureUnit": "F",
        ...
      },
      ...
    ]
  }
}

Чтобы вычислить среднюю прогнозируемую температуру, нужно разобрать документ, перейти к тому месту структуры, где находятся прогнозы, и пройти по массиву прогнозов, извлекая данные о температуре. У нас должна быть возможность решать такие простые задачи простым кодом на Java, не устанавливая внешнюю библиотеку и не подозревая, что на другом языке мы работали бы продуктивнее.

Одна из ключевых целей, определяющих развитие платформы Java в последнее время, — дать возможность решать простые задачи проще и с меньшими формальностями. Этой цели служат, в частности, удобные фабричные методы для коллекций, объявления var, запуск программ из исходных файлов, а также Compact Source Files (компактные исходные файлы) и Instance Main Methods (экземплярные методы main). Простой JSON API для разбора и генерации JSON-документов тоже служил бы этой важной цели.

Использование JSON в JDK

Стандартный JSON API в платформе Java также открыл бы путь к более широкому использованию JSON в платформе и в самом JDK, поскольку у JDK не может быть внешних зависимостей. Один из возможных сценариев — конфигурационные файлы. JDK использует формат файлов свойств для различных конфигурационных файлов, например файлов свойств безопасности. Слабость этого формата в том, что он не может выражать структурированные данные. Чтобы представить массив в файле свойств, приходится прибегать к неуклюжим обходным путям, например к последовательно пронумерованным свойствам:

security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
...

Если JSON будет встроен в JDK, конфигурационные файлы смогут естественным образом представлять массивы с помощью массивов JSON:

{
  "providers": [ "SUN", "SunRsaSign", "SunEC" ],
  ...
}

Описание

API jdk.incubator.json построен вокруг интерфейса JsonValue, который представляет значение JSON.

В синтаксисе JSON есть четыре вида примитивов:

  1. строки JSON, ограниченные двойными кавычками:

    "Hello"
    "My name is 'Bob'"
    "\u006a\u0061\u0076\u0061"
  2. числа JSON, записанные в системе счисления с основанием 10 с помощью десятичных цифр:

    6  6.0  31.84  2.9E+5
  3. логические литералы JSON: true и false

  4. литерал null в JSON: null

и два вида структур:

  1. объекты JSON, ограниченные символами { } и состоящие из членов, разделённых запятыми. Член имеет имя, которое также называют ключом, и значение, разделённые двоеточием:

    {
      "address" : "123 Smith Street",
      "value" : 31.84,
      "coordinates" : [ [ 37, 23, 41 ], [ -121, 57, 10 ] ]
    }
  2. массивы JSON, ограниченные символами [ ] и состоящие из значений JSON, разделённых запятыми:

    [ 1, 2, 3, { "value": "4" }, [ 5, 6 ] ]

Соответственно, у интерфейса JsonValue есть шесть подынтерфейсов: JsonString, JsonNumber, JsonBoolean, JsonNull, JsonObject и JsonArray. Каждый интерфейс объявляет операции, подходящие для соответствующего ему синтаксического элемента JSON: экземпляры JsonNumber и JsonBoolean предоставляют преобразования в примитивные типы Java, экземпляры JsonString — преобразование в String Java, экземпляры JsonObject открывают доступ к членам, а экземпляры JsonArray — к элементам массива.

Интерфейс JsonValue объявлен как sealed, что гарантирует: любой экземпляр JsonValue всегда относится к одному из этого фиксированного набора подтипов, поэтому исчерпывающим выражениям и операторам switch не требуется ветка default.

JSON API упрощает разбор JSON-документов, соответствующих RFC 8259. Метод parse класса Json возвращает дерево экземпляров JsonValue, которые предоставляют имена, типы и значения разобранных данных JSON. Возвращаясь к примеру с National Weather Service, мы можем вычислить среднюю прогнозируемую температуру всего в несколько строк:

String body = ... REST response body, which is a JSON document ... ;
JsonValue json = Json.parse(body);
json.get("properties").get("periods").asList().stream()
    .mapToInt(j -> j.get("temperature").asInt())
    .average()
    .ifPresent(IO::println);

(Полный пример приведён в Приложении.)

API также упрощает генерацию JSON-документов. Например, этот код:

IO.println(JsonObject.of(Map.of("providers",
                                JsonArray.of(List.of(JsonString.of("SUN"),
                                                     JsonString.of("SunRsaSign"),
                                                     JsonString.of("SunEC"))))));

выводит:

{"providers":["SUN","SunRsaSign","SunEC"]}

Разбор JSON-документов и перемещение по ним

Класс Json может разобрать JSON-документ, содержащийся либо в String, либо в массиве char. JSON-документ может быть телом ответа REST API, прочитанным из сети, конфигурационным файлом, прочитанным с диска, или другими текстовыми данными, сформированными приложением.

Для разбора JSON-документа достаточно одного вызова одного из методов Json.parse:

JsonValue root = Json.parse(doc);

Разбор строгий: документ должен соответствовать RFC 8259. Расширения синтаксиса, такие как завершающие запятые и комментарии, не поддерживаются. Кроме того, в документах не должно быть объектов с повторяющимися именами членов. Эта политика, допускаемая RFC, обеспечивает максимальную совместимость и предсказуемость и снижает опасения, связанные с обработкой некорректных или неоднозначных JSON-документов. (Подробное обсуждение см. ниже.)

При успешном разборе возвращается экземпляр JsonValue. При неудачном разборе выбрасывается непроверяемое исключение JsonParseException. Исключение содержит подробное сообщение с конкретными сведениями об ошибке, её путём от корня документа и её положением в документе. Например, если документ содержит в объекте повторяющиеся имена членов, выбрасываемое исключение имеет вид:

jdk.incubator.json.JsonParseException: The duplicate member name: "providers" was
already parsed. Path: "{". Location: line 2, position 4.

В корне большинства JSON-документов находится объект JSON или массив JSON. Например, дамп потоков в формате JSON, созданный инструментом jcmd, содержит корневой объект:

{
  "threadDump": {
    "formatVersion": 2,
    "processId": 45178,
    "time": "2026-04-16T23:13:02.709630Z",
    "runtimeVersion": "27-internal",
    "threadContainers": [
      {
        "container": "<root>",
        "parent": null,
        "owner": null,
        "threads": [
          {
            "tid": 3,
            "time": "2026-04-16T23:13:02.906891Z",
            "name": "main",
            "state": "WAITING",
            ...

Корневой объект содержит единственный член — вложенный объект threadDump, а threadDump, в свою очередь, содержит как примитивные, так и структурные значения JSON.

Получив корневой JsonValue через Json.parse(...), вы можете извлекать значения из объектов и массивов с помощью их методов доступа, которые возвращают запрошенное значение члена или элемент массива как JsonValue. Для доступа к значению члена не нужно приводить JsonValue к JsonObject, а для доступа к элементу массива — к JsonArray.

  • get(String) получает значение члена объекта. Получение объекта дампа потоков:

    JsonValue threadDump = root.get("threadDump");
  • get(int) получает элемент массива. Получение корневого контейнера потоков:

    JsonValue firstContainer = threadDump.get("threadContainers").get(0);

Если экземпляр JsonValue имеет неподходящий тип или запрошенный член или элемент не существует, методы доступа выбрасывают JsonValueException.

Преобразование значений JSON в значения Java

Значение JSON можно преобразовать в значение Java, вызвав один из методов преобразования интерфейса JsonValue. Чтобы преобразование прошло успешно, JsonValue должен быть экземпляром соответствующего подтипа JsonValue:

Подтип Метод Итоговый тип Java
JsonString asString() java.lang.String
JsonNumber asInt() int
JsonNumber asLong() long
JsonNumber asDouble() double
JsonBoolean asBoolean() boolean
JsonObject asMap() java.util.Map
JsonArray asList() java.util.List

Например, можно получить значение Java String, связанное с членом «time» дампа потоков:

JsonValue threadDumpTime = threadDump.get("time");
String time = threadDumpTime.asString();

Можно преобразовать массив контейнеров потоков в List экземпляров JsonValue и обработать каждый экземпляр:

threadDump.get("threadContainers").asList().forEach(jv -> ...);

Можно обратиться к объекту дампа потоков как к Map, чтобы получить количество членов:

int count = threadDump.asMap().size();

Можно углубляться в JSON-документ, выстраивая цепочку вызовов методов доступа и преобразуя результат в значение Java только в конце. Получение значения идентификатора первого потока в корневом контейнере потоков:

long tid = threadDump.get("threadContainers").get(0)
                     .get("threads").get(0).get("tid").asLong();

Устройство методов преобразования избавляет от большинства проверок instanceof и нисходящих приведений типов в случаях, когда в документе ожидается конкретный тип данных JSON:

  • asString() преобразует экземпляр JsonString в String Java, заменяя escape-последовательности JSON из RFC 8259 соответствующими символами.

  • asInt() преобразует экземпляр JsonNumber в int Java, если его числовое значение может быть представлено точно.

  • asLong() преобразует экземпляр JsonNumber в long Java, если его числовое значение может быть представлено точно.

  • asDouble() преобразует экземпляр JsonNumber в double Java, если его числовое значение может быть представлено с достаточной точностью.

  • asBoolean() преобразует экземпляр JsonBoolean в значение Java boolean, равное true или false.

  • asMap() преобразует экземпляр JsonObject в неизменяемый Map Java. Если объект JSON не содержит членов, возвращается пустой Map.

  • asList() преобразует экземпляр JsonArray в неизменяемый List Java. Если массив JSON не содержит элементов, возвращается пустой List.

Для значения null в JSON метода преобразования нет. Экземпляры JsonNull можно обрабатывать с помощью проверки instanceof JsonNull или метода tryValue.

Если JsonValue не является экземпляром подтипа, подходящего для метода преобразования, метод выбрасывает JsonValueException. Например, вызов asInt() для JsonValue, который является экземпляром JsonString, всегда будет выбрасывать это исключение. Попытки разобрать строковое значение как число не предпринимается.

Числовые преобразования могут завершиться неудачей, например, если числовое значение невозможно представить в целевом числовом типе Java; в этом случае тоже выбрасывается JsonValueException. Более подробное обсуждение обработки чисел и преобразований см. ниже.

Обработка изменений JSON-документов

JSON-документы из определённого источника со временем могут меняться так, что это нарушит ваши прежние ожидания относительно их структуры и содержимого:

  • Вы можете вызвать методы доступа, рассчитывая на имена членов или индексы массивов, которых нет в объектах JSON и массивах JSON документа.

  • Вы можете вызвать методы преобразования, применимые к одному типу JSON, для значений другого типа.

Если вызвать методы доступа или преобразования для неподходящего типа, они выбрасывают JsonValueException. Это исключение непроверяемое, чтобы скрипты и небольшие программы было проще читать и писать.

Продолжим пример с дампом потоков: напомним, что корневое значение JSON — это объект JSON с единственным членом threadDump. Этот код:

JsonValue name = root.get("threadName");

выбрасывает JsonValueException с сообщением о том, что объект JSON не содержит члена с именем "threadName", а этот код:

List<JsonValue> threadDumpList = threadDump.asList();

выбрасывает JsonValueException с сообщением о том, что член threadDump является объектом JSON, а не массивом JSON.

Сообщение исключения описывает путь от корня документа JSON к неожиданному значению JSON, а также позицию в документе JSON. Это полезно, когда цепочка методов доступа уходит глубоко в документ. Например, если бы в приведённом ранее фрагменте кода, извлекающем идентификатор потока, он по ошибке преобразовывался в boolean, а не в long:

boolean tid = threadDump.get("threadContainers").get(0)
                        .get("threads").get(0).get("tid").asBoolean();

то исключение, выброшенное asBoolean(), имело бы вид:

jdk.incubator.json.JsonValueException: JsonNumber is not a JsonBoolean. Path:
 "{threadDump{threadContainers[0{threads[0{tid". Location: line 13, position 19.

Обработка необязательных членов

Если вы не знаете, есть ли в объекте JSON член с заданным именем, можно использовать метод доступа tryGet. Он возвращает экземпляр Optional, содержащий значение члена, или пустой Optional, если такого члена нет. (Метод get, напротив, проверяет, что член существует, и выбрасывает исключение, если его нет.) Метод tryGet выбрасывает JsonValueException, если он вызван не для JsonObject.

Рассмотрим следующий объект потока:

{
  "tid": 11,
  "time": "2026-04-16T23:13:02.918321Z",
  "name": "Finalizer",
  "state": "WAITING",
  "waitingOn": "java.lang.Object@c10f5b9",
  "stack": [
    ...

Объект потока содержит несколько необязательных членов. Один из них — член waitingOn, который содержит строковое представление JSON объекта, которого ожидает поток. Однако если поток ничего не ожидает, объект потока может выглядеть так:

{
  "tid": 10,
  "time": "2026-04-16T23:13:02.918177Z",
  "name": "Reference Handler",
  "state": "RUNNABLE",
  "stack": [
    ...

Поэтому при обработке объектов потоков из дампа потоков нужно быть готовым к тому, что член waitingOn отсутствует. Это можно обработать с помощью tryGet:

JsonValue thread = ...
thread.tryGet("waitingOn")
      .ifPresent(result -> ...);

Лямбда-выражение, переданное в ifPresent, вызывается, только если член waitingOn присутствует.

Обработка значений null

Если вы не знаете, является ли значение JSON значением null JSON, можно использовать метод доступа tryValue. Этот метод возвращает пустой Optional, если значение JSON, для которого он вызван, является JsonNull; в противном случае он возвращает это значение.

Например, объект контейнера потоков обычно выглядит так:

{
  "container": "java.util.concurrent.ThreadPoolExecutor@1936a586",
  "parent": "<root>",
  ...

Здесь значение члена parent — строка JSON, имя родительского контейнера. Однако контейнер с именем "<root>" является корнем всех контейнеров и выглядит так:

{
  "container": "<root>",
  "parent": null,
  ...

У корневого контейнера нет родителя, поэтому значение члена parent — null JSON. Поэтому при обработке объектов контейнеров из дампа потоков нужно быть готовым к тому, что член parent окажется либо строкой JSON, либо null JSON. Это можно обработать с помощью tryValue:

JsonValue container = ...
container.get("parent").tryValue()
         .ifPresent(result -> ...);

Лямбда-выражение, переданное в ifPresent, вызывается, только если значение члена "parent" не является null JSON.

Обработка изменчивой структуры и содержимого

Структура и содержимое документов JSON в определённом контексте часто единообразны, но иногда они меняются. Они могут различаться у разных источников, со временем меняться у одного источника, который сам развивается, или даже различаться внутри одного документа.

Например, в дампах потоков в JDK 26 и более ранних выпусках идентификаторы потоков представлены строками JSON; в JDK 27 и более поздних выпусках идентификаторы потоков представлены числами JSON.

Код, который ожидает, что tid будет числом JSON, например:

long tid = thread.get("tid").asLong();

завершится с JsonValueException, если встретит дамп потоков, созданный версией JDK, которая выводит значения tid в виде строк JSON.

В любом из представлений числовое значение по спецификации помещается в Java-тип long. Можно использовать instanceof, чтобы проверить, что перед вами — JsonNumber или JsonString, но понятнее использовать шаблоны типов в операторе switch:

long tid = switch (thread.get("tid")) {
    case JsonNumber jn -> jn.asLong();
    case JsonString js -> Long.parseLong(js.asString());
    default -> throw new JsonValueException("Unexpected type for \"tid\"");
};

Генерация документов JSON

Чтобы получить документ JSON в строковом виде из JsonValue, достаточно вызвать его метод toString. Этот метод возвращает компактное строковое представление, в котором все члены, элементы и значения выводятся в одной строке без пробельных символов между ними.

Например, этот код:

JsonValue json = Json.parse("""
  {
    "service" : "web_server",
    "id" : 3
  }
  """);
IO.println(json.toString());

выводит:

{"service":"web_server","id":3}

(Метод toString отличается от метода asString, который выбрасывает исключение, если JsonValue, для которого он вызван, не является JsonString.)

Статический метод Json.toDisplayString выводит документ JSON в отформатированном виде: члены и элементы разделяются переводами строк, а вложенные структуры получают отступ заданной величины String. Например, этот код:

IO.println(Json.toDisplayString(json, "  "));

выводит приведённую выше структуру с отступом в два пробела:

{
  "service": "web_server",
  "id": 3
}

Вывод обоих методов, toString и Json.toDisplayString, может быть разобран методом Json.parse, который создаст JsonValue, представляющий то же значение JSON, что и в исходном документе.

Числа JSON

Синтаксис чисел JSON, определённый в RFC 8259, позволяет представлять десятичные значения произвольной точности и диапазона. JSON API позволяет обрабатывать числа JSON без потерь; однако в большинстве приложений достаточно обычных числовых типов.

RFC 8259 указывает, что хорошей совместимости между библиотеками JSON можно добиться, используя 64-битные двоичные значения с плавающей точкой IEEE 754, которые соответствуют Java-типу double. Поэтому метод asDouble() преобразует числовое значение JSON в Java-тип double. Значение JSON должно лежать в диапазоне, который может представить double; если значение выходит за пределы диапазона, выбрасывается JsonValueException. Значения бесконечности и «не число» ("NaN") в JSON непредставимы, поэтому никогда не возвращаются. Отрицательный ноль, однако, в JSON представим, поэтому может быть возвращён.

Если точность значения JSON выше, чем может представить double, значение округляется до ближайшего значения double. Например:

double d1 = Json.parse("3.141592653589793238462643383279").asDouble();
// d1 is 3.141592653589793, the nearest double value

double d2 = Json.parse("1.8E309").asDouble();
// Throws JsonValueException, out of range.
// Double.parseDouble("1.8E309") would yield positive Infinity.

Целочисленные значения используются часто, поэтому метод asInt() преобразует числовое значение JSON в Java-значение int. Значение JSON должно точно представляться как int, иначе выбрасывается исключение. Числа, у которых синтаксически есть дробная часть, но которые представляют целые значения, преобразуются; например:

int i1 = Json.parse("123.0").asInt();       // succeeds
int i2 = Json.parse("234.56E2").asInt();    // succeeds
int i3 = Json.parse("345.6").asInt();       // fails, not integral
int i4 = Json.parse("2147483648").asInt();  // fails, out of range

Метод преобразования asLong() похож на asInt(), но возвращает Java-значение long и поддерживает любое числовое значение JSON, которое может быть точно представлено как long.

Если нужен более узкий примитивный тип, чем int или double, для безопасного преобразования можно использовать Primitive Types in Patterns (примитивные типы в шаблонах) — сейчас это возможность в статусе Preview (предварительная версия). Например, если вы ожидаете, что число JSON представимо как short:

JsonValue json = Json.parse("""
  {
    "id": 12345,
    "price": 10.99
  }
  """);
if (json.get("id").asInt() instanceof short s) {
    // use s
} else {
    // report out-of-range error
}

Как уже говорилось, числа JSON могут иметь произвольную точность и диапазон. Методы asDouble(), asInt() и asLong() по определению обрабатывают только подмножество числовых значений JSON: они отвергают значения вне диапазона и округляют значения с избыточной точностью. Чтобы обрабатывать числовые данные JSON без потери информации, можно преобразовать практически любое число JSON в экземпляр java.math.BigDecimal:

BigDecimal bd = new BigDecimal(jn.toString());

Альтернативы

Тестирование

Мы тщательно протестируем JSON API, чтобы убедиться, что разбирать и генерировать можно только канонические формы JSON по RFC 8259. Это поможет гарантировать, что использование API не приведёт к несогласованности при взаимодействии с другими библиотеками JSON. Для этого мы не только добавим в JDK всесторонние модульные тесты, но и воспользуемся устоявшимся набором тестов JSON Parsing Test Suite, который содержит множество входных данных для граничных случаев.

Риски и допущения

  • Мы предполагаем, что входные документы JSON помещаются в память — в виде String или массива char, — и что результат разбора тоже помещается в память. При нашей модели на основе дерева, если бы мы разрешили такие источники JSON, как файлы или сетевые соединения, при больших документах могли бы возникать проблемы, например нехватка памяти. Это решение соответствует нашей минималистичной философии проектирования.

  • Риск этого предложения в том, что новый API может начать использоваться в приложениях, которые уже используют внешние библиотеки JSON, что приведёт к беспорядку и путанице. Мы считаем, что преимущества перевешивают этот риск.

  • Пока API находится в статусе Incubator, мы соберём больше информации о сценариях использования, связанных с генерацией и преобразованием документов JSON, чтобы развивать эти части API. Кроме того, мы продолжим учитывать готовящиеся возможности языка для Pattern Matching (сопоставление с образцом), которые могут повлиять на дизайн API.

Приложение: пример с прогнозом погоды

Следующая программа отправляет запрос к U.S. National Weather Service REST API за прогнозом погоды на семь дней для Санта-Клары (Калифорния). В теле ответа она получает документ JSON. Затем программа разбирает документ, переходит вглубь его структуры и получает массив прогнозов. После этого она извлекает температуру из каждого прогноза, вычисляет среднее значение и выводит результат.

import java.net.*;
import java.net.http.*;
import jdk.incubator.json.Json;
import jdk.incubator.json.JsonValue;

void main() throws Exception {
    var query = "https://api.weather.gov/gridpoints/MTR/97,83/forecast";
    var client = HttpClient.newHttpClient();
    var request = HttpRequest.newBuilder(URI.create(query)).build();
    var response = client.send(request, HttpResponse.BodyHandlers.ofString());
    JsonValue json = Json.parse(response.body());
    json.get("properties").get("periods").asList().stream()
        .mapToInt(j -> j.get("temperature").asInt())
        .average()
        .ifPresent(IO::println);
}

Включение API в статусе Incubator

JSON API в настоящее время является Incubator-модулем и по умолчанию отключён. Чтобы его использовать, нужно включить его параметром командной строки --add-modules jdk.incubator.json, который добавляет Incubator-модуль в набор модулей, доступных для разрешения. Чтобы запустить приведённую выше программу-пример, этот параметр нужно указать и при компиляции, и при запуске.

Чтобы запустить программу для среднего прогноза как программу из одного файла исходного кода, выполните:

$ java --add-modules jdk.incubator.json Weather.java

Вывод будет примерно таким:

WARNING: Using incubator modules: jdk.incubator.json
53.357142857142854

Чтобы скомпилировать программу с помощью javac и запустить её с помощью java, выполните:

$ javac --add-modules jdk.incubator.json Weather.java
$ java --add-modules jdk.incubator.json Weather

Чтобы интерактивно поэкспериментировать с API, можно использовать jshell. Как и раньше, Incubator-модуль нужно включить в командной строке:

$ jshell --add-modules jdk.incubator.json
jshell> import jdk.incubator.json.*
jshell> Json.parse("""
   ...> { "name": "Today", "temperature": 54 }
   ...> """)
$2 ==> {"name":"Today","temperature":54}
jshell> $2.get("temperature").asInt()
$3 ==> 54
jshell>