ObjectMapper.writeValueAsString: примеры (JAVA)

Разбор применения writeValueAsString
Раздел: Работа с JSON (Jackson, Gson, JSON-P)
ObjectMapper.writeValueAsString(Object value): String

Описание метода и его контракт

В Jackson класс com.fasterxml.jackson.databind.ObjectMapper предоставляет метод writeValueAsString, который выполняет сериализацию объекта Java в строку в формате JSON.

Подпись метода:

public String writeValueAsString(Object value) throws com.fasterxml.jackson.core.JsonProcessingException

Параметры и поведение:

  • value - объект для сериализации. Может быть null, примитивом, массивом, коллекцией, отображением (Map), POJO, Java Date/Time и т. п. Для некоторых типов требуется подключение модулей (например, JavaTimeModule для java.time).
  • Возвращаемое значение - строка JSON (String), содержащая сериализованное представление объекта. Для null возвращается строка "null" (в формате JSON), а не null Java-ссылка.
  • Исключения - метод бросает JsonProcessingException (или подклассы), если сериализация невозможна (например, нет сериализатора для типа, рекурсивная ссылка без обработки, ошибки преобразования).

Контекст использования:

  • Преобразование объектов в JSON для HTTP-ответов, логирования, кеширования, хранения конфигураций и передачи через сообщения.
  • Быстрая сериализация при отладке, когда требуется получить JSON в виде строки.

Важные настройки, влияющие на результат:

  • SerializationFeature (например, INDENT_OUTPUT для форматирования, WRITE_DATES_AS_TIMESTAMPS для дат).
  • PropertyNamingStrategy для изменения имён полей в JSON (пример: snake_case).
  • Модули - регистрация модулей (например, JavaTimeModule, ParameterNamesModule, кастомные модули).
  • MixIn-аннотации и custom serializers для изменения сериализации без изменения исходного класса.

Особенности потокобезопасности: экземпляр ObjectMapper рекомендуется настраивать один раз и переиспользовать. После завершения конфигурации он безопасен для доступа из разных потоков.

Короткие примеры использования

1) Простейшая сериализация POJO:

import com.fasterxml.jackson.databind.ObjectMapper;

public class User { public String name; public int age; }

ObjectMapper om = new ObjectMapper();
User u = new User(); u.name = "Ivan"; u.age = 30;
String json = om.writeValueAsString(u);
System.out.println(json);
{"name":"Ivan","age":30}

2) Сериализация коллекции:

List<String> list = Arrays.asList("a","b","c");
String json = new ObjectMapper().writeValueAsString(list);
System.out.println(json);
["a","b","c"]

3) Pretty print (красивый вывод):

ObjectMapper om = new ObjectMapper();
om.enable(com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT);
Map<String,Object> map = new HashMap<>();
map.put("ok", true); map.put("count", 5);
String json = om.writeValueAsString(map);
System.out.println(json);
{
  "ok" : true,
  "count" : 5
}

4) Дата и время (без модуля):

Map<String,Object> m = Collections.singletonMap("date", java.time.LocalDate.now());
String j = new ObjectMapper().writeValueAsString(m);
System.out.println(j);
{"date":{...}} // без JavaTimeModule результат может быть неинтуитивен или приведёт к ошибке сериализации, зависит от версии Jackson

5) Пользовательский сериализатор (микропример):

public class BoolAsIntSerializer extends com.fasterxml.jackson.databind.JsonSerializer<Boolean> {
  @Override public void serialize(Boolean value, com.fasterxml.jackson.core.JsonGenerator gen,
    com.fasterxml.jackson.databind.SerializerProvider serializers) throws java.io.IOException {
    gen.writeNumber(value ? 1 : 0);
  }
}

ObjectMapper om = new ObjectMapper();
SimpleModule mod = new SimpleModule();
mod.addSerializer(Boolean.class, new BoolAsIntSerializer());
om.registerModule(mod);
String s = om.writeValueAsString(Collections.singletonMap("flag", true));
System.out.println(s);
{"flag":1}

Похожие подходы в Java и их отличия

1) Gson (com.google.gson.Gson): простая и компактная альтернатива. Проще в настройке, но меньше возможностей по расширенной кастомизации и интеграции с модулями Jackson. Пример: new Gson().toJson(obj). Подходит для простых случаев.

2) JSON-B (Jakarta JSON Binding, Jsonb): стандартная спецификация для привязки Java↔JSON. Менее популярна, чем Jackson, но хороша при желании следовать стандартам Jakarta EE.

3) org.json (JSONObject#toString): минималистичный подход, полезен для простых задач или когда нужен быстрый парсер/строитель JSON без сложной сериализации POJO.

4) Внутренние методы ObjectMapper: writeValue(File, obj), writeValue(OutputStream, obj), writeValueAsBytes(obj). Они отличаются целью вывода (файл, поток, байты) и могут быть предпочтительнее, когда требуются потоки или бинарный буфер.

Выбор основывается на требованиях: сложная сериализация, аннотации и интеграция с экосистемой - Jackson; компактность и простота - Gson; совместимость со стандартом Jakarta - JSON-B.

Как это делается в других языках

PHP - json_encode:

$arr = ['name' => 'Ivan', 'age' => 30];
echo json_encode($arr);
{"name":"Ivan","age":30}

JavaScript - JSON.stringify:

const obj = { name: 'Ivan', age: 30 };
console.log(JSON.stringify(obj));
{"name":"Ivan","age":30}

Python - json.dumps:

import json
print(json.dumps({'name':'Ivan','age':30}))
{"name": "Ivan", "age": 30}

C# - Newtonsoft.Json и System.Text.Json:

// Newtonsoft
var json = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
// System.Text.Json
var json2 = System.Text.Json.JsonSerializer.Serialize(obj);
{"name":"Ivan","age":30}

Go - encoding/json:

b, _ := json.Marshal(map[string]interface{}{"name":"Ivan","age":30})
fmt.Println(string(b))
{"name":"Ivan","age":30}

Kotlin - можно использовать Jackson или kotlinx.serialization:

// Jackson
val json = jacksonObjectMapper().writeValueAsString(data)
// kotlinx.serialization
val json2 = Json.encodeToString(data)
{"name":"Ivan","age":30}

Lua - cjson:

local cjson = require 'cjson'
print(cjson.encode({name='Ivan', age=30}))
{"name":"Ivan","age":30}

SQL (пример PostgreSQL) - функции для JSON-формирования:

SELECT row_to_json(t) FROM (SELECT 'Ivan' AS name, 30 AS age) t;
{"name":"Ivan","age":30}

Отличия от Java/Jackson: в большинстве языков сериализация возвращает строку напрямую и имеет более простые механизмы расширения. Jackson выделяется богатой системой модулей, аннотациями и возможностью тонкой настройки сериализации и десериализации.

Типичные ошибки при использовании и примеры

1) Отсутствие сериализатора для типа (например, для некоторых сторонних классов):

class NoSerializerHolder { Object value; }
ObjectMapper om = new ObjectMapper();
NoSerializerHolder h = new NoSerializerHolder();
h.value = new java.sql.Connection() { /* интерфейс, не сериализуемый */ };
om.writeValueAsString(h);
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.sql.Connection ...

Решение: реализовать/зарегистрировать кастомный сериализатор или исключить поле с помощью аннотаций/микса.

2) Бесконечная рекурсия при сериализации взаимно ссылающихся объектов:

class A { public B b; }
class B { public A a; }
A a = new A(); B b = new B(); a.b = b; b.a = a;
new ObjectMapper().writeValueAsString(a);
com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError ...) or similar

Варианты решения: использовать @JsonIdentityInfo, @JsonManagedReference/@JsonBackReference, или отключить сериализацию некоторых полей.

3) Неправильная сериализация java.time без модуля:

Map<String,Object> m = Map.of("dt", java.time.LocalDateTime.now());
new ObjectMapper().writeValueAsString(m);
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type not supported by default

Решение: зарегистрировать JavaTimeModule и настроить формат даты.

4) Неправильная конфигурация default typing (полиморфная сериализация):

ObjectMapper om = new ObjectMapper();
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.NON_FINAL);
String s = om.writeValueAsString(List.of(1,2,3));
JSON содержит служебную информацию о типах, что может быть нежелательно и создавать проблемы с безопасностью

Решение: избегать включения default typing без проверки PolymorphicTypeValidator и понимать последствия для безопасности.

Изменения и важные приметы в последних релизах

  • Поддержка Java-records и улучшенная интеграция с современными конструкциями Java появилась в релизах Jackson 2.12 и выше - это упростило сериализацию/десериализацию record-классов.
  • Изменён подход к безопасной полиморфной десериализации: старые методы вроде enableDefaultTyping считаются небезопасными; рекомендуется использовать PolymorphicTypeValidator при необходимости полиморфной десериализации.
  • Работа с Java 8+ датами улучшена: распространённый паттерн - регистрация JavaTimeModule и настройка SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.
  • Постепенные оптимизации производительности и API-фиксы; сигнатура writeValueAsString устойчива и изменений в ней нет, но поведение может зависеть от более новых модулей и настроек по умолчанию.

Расширенные и нетипичные сценарии

1) Сериализация LocalDateTime с форматом ISO:

Пример java
ObjectMapper om = new ObjectMapper();
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule jtm = new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule();
jtm.addSerializer(java.time.LocalDateTime.class,
  new com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME));
om.registerModule(jtm);
om.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
String s = om.writeValueAsString(java.time.LocalDateTime.of(2023,1,2,15,30));
System.out.println(s);
"2023-01-02T15:30:00"

2) Фильтрация полей с помощью SimpleBeanPropertyFilter:

Пример java
public class User { public String name; public String secret; }
ObjectMapper om = new ObjectMapper();
FilterProvider filters = new com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider()
  .addFilter("myFilter", com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter.serializeAllExcept("secret"));
om.setFilterProvider(filters);
// MixIn для привязки фильтра к классу
om.addMixIn(User.class, UserFilterMixIn.class);

User u = new User(); u.name = "Ivan"; u.secret = "x";
String out = om.writeValueAsString(u);
System.out.println(out);

// MixIn
@interface UserFilterMixIn { }
@com.fasterxml.jackson.annotation.JsonFilter("myFilter")
interface UserFilterMixIn { }
{"name":"Ivan"}

3) Обход циклических ссылок с @JsonIdentityInfo:

Пример java
@com.fasterxml.jackson.annotation.JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
class Node { public int id; public Node parent; }

Node a = new Node(); a.id = 1;
Node b = new Node(); b.id = 2; b.parent = a; a.parent = b;
String s = new ObjectMapper().writeValueAsString(a);
System.out.println(s);
{"id":1,"parent":{"id":2,"parent":1}}

4) Сериализация с разными View (JsonView):

Пример java
class Data {
  @com.fasterxml.jackson.annotation.JsonView(Views.Public.class)
  public String name;
  @com.fasterxml.jackson.annotation.JsonView(Views.Internal.class)
  public String secret;
}
ObjectMapper om = new ObjectMapper();
String pub = om.writerWithView(Views.Public.class).writeValueAsString(data);
String full = om.writerWithView(Views.Internal.class).writeValueAsString(data);
pub: {"name":"..."}
full: {"name":"...","secret":"..."}

5) Сериализация JSON-дерева (JsonNode):

Пример java
com.fasterxml.jackson.databind.node.ObjectNode node = new com.fasterxml.jackson.databind.node.ObjectNode(com.fasterxml.jackson.databind.node.JsonNodeFactory.instance);
node.put("x", 1);
String s = new ObjectMapper().writeValueAsString(node);
System.out.println(s);
{"x":1}

6) Полиморфная сериализация с безопасной валидацией:

Пример java
ObjectMapper om = new ObjectMapper();
com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator ptv =
  com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator.builder()
    .allowIfBaseType("com.example.myapp")
    .build();
om.activateDefaultTyping(ptv, com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.NON_FINAL);
String s = om.writeValueAsString(List.of("a"));
System.out.println(s);
["a"] // при включённом default typing будут добавлены метаданные типов только для нестандартных случаев

7) Преобразование больших структур без выделения лишней памяти: использование ObjectWriter и потоков для записи прямо в OutputStream вместо получения строки, когда нужно избежать аллокации большой строки.

Пример java
try (OutputStream os = new FileOutputStream("out.json")) {
  new ObjectMapper().writer().writeValue(os, largeObject);
}
// Данные записаны в файл без получения одной огромной String

джава ObjectMapper.writeValueAsString function comments

En
ObjectMapper.writeValueAsString Serializes a Java object into its equivalent JSON representation