docs(blogs): add Jackson JSON flattening guide

- Add @JsonUnwrapped and @JsonAnyGetter usage examples with code
- Include comparison table for choosing between annotations
- Sync content across zh-hans and en-gb versions
This commit is contained in:
zihluwang committed 2026-07-30 09:39:59 +08:00
1 parent 56429e0449
commit 25a50486cb
2 files changed
+144

No files matched your search

@@ -207,4 +207,75 @@ Specification<User> spec = (root, query, cb) -> {
Page<User> page = userRepository.findAll(spec, pageable); Page<User> page = userRepository.findAll(spec, pageable);
``` ```
## Flattening Objects or Maps into JSON with Jackson
When serialising to JSON, you sometimes need to "flatten" a nested object's properties into the parent JSON rather than having them appear as a nested field. Jackson provides two annotations for handling objects and `Map` flattening separately.
### `@JsonUnwrapped` — flattening objects with a known structure
`@JsonUnwrapped` expands the properties of a nested object directly into the parent JSON:
```java
public class Address {
private String street;
private String city;
// getters and setters
}
public class User {
private String name;
@JsonUnwrapped
private Address address;
// getters and setters
}
```
Serialised output:
```json
{
"name": "John Doe",
"street": "Oxford Street",
"city": "London"
}
```
Without `@JsonUnwrapped`, `address` would appear as a nested JSON object: `"address": { "street": "...", "city": "..." }`.
### `@JsonAnyGetter` — flattening a Map with dynamic keys
`@JsonAnyGetter` flattens a `Map`'s key-value pairs directly into the parent JSON, ideal for dynamic or open-ended fields:
```java
public class Product {
private String name;
private Map<String, Object> attributes = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> getAttributes() {
return attributes;
}
// other getters and setters
}
```
With `attributes` containing `{"colour": "red", "size": "XL"}`, the serialised output is:
```json
{
"name": "Example Product",
"colour": "red",
"size": "XL"
}
```
Note that `@JsonAnyGetter` must be placed on the **getter method**, not the field itself. You will also need `@JsonAnySetter` to support deserialisation.
### How to choose
| Scenario | Annotation |
|-------------------------------------------------------|--------------------|
| Flatten nested objects with fixed fields (e.g. address, config) | `@JsonUnwrapped` |
| Flatten dynamic key-value pairs with unknown keys | `@JsonAnyGetter` |
@@ -201,3 +201,76 @@ Specification<User> spec = (root, query, cb) -> {
Page<User> page = userRepository.findAll(spec, pageable); Page<User> page = userRepository.findAll(spec, pageable);
``` ```
## 使用 Jackson 将对象或 Map 平铺到 JSON 中
在序列化 JSON 时,有时需要将嵌套对象的属性"平铺"到父级 JSON 中,而不是作为一个嵌套字段出现。Jackson 提供了两个注解来分别处理对象和 `Map` 的平铺场景。
### `@JsonUnwrapped` — 平铺已知结构的对象
`@JsonUnwrapped` 会将一个嵌套对象的属性直接展开到父级 JSON 中:
```java
public class Address {
private String street;
private String city;
// getters and setters
}
public class User {
private String name;
@JsonUnwrapped
private Address address;
// getters and setters
}
```
序列化结果:
```json
{
"name": "陳大文",
"street": "彌敦道",
"city": "香港"
}
```
如果不使用 `@JsonUnwrapped`,`address` 会作为一个嵌套 JSON 对象出现:`"address": { "street": "...", "city": "..." }`。
### `@JsonAnyGetter` — 平铺键值不固定的 Map
`@JsonAnyGetter` 可以将 `Map` 中的键值对直接平铺到父级 JSON 中,适合 key 不固定或动态扩展的场景:
```java
public class Product {
private String name;
private Map<String, Object> attributes = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> getAttributes() {
return attributes;
}
// other getters and setters
}
```
当 `attributes` 包含 `{"colour": "red", "size": "XL"}` 时,序列化结果:
```json
{
"name": "Example Product",
"colour": "red",
"size": "XL"
}
```
注意 `@JsonAnyGetter` 需要加在 **getter 方法**上,而不是字段上;同时需要搭配 `@JsonAnySetter` 来支持反序列化。
### 如何选择
| 场景 | 注解 |
|----------------------------------------|--------------------|
| 平铺字段固定的嵌套对象(如地址、配置) | `@JsonUnwrapped` |
| 平铺 key 不固定的动态键值对 | `@JsonAnyGetter` |