0
# Object Structure
1
2
Annotations that modify how objects are structured in JSON, including unwrapping nested objects and controlling value representation.
3
4
## Capabilities
5
6
### JsonUnwrapped
7
8
Unwrap nested object properties into the parent object.
9
10
```java { .api }
11
/**
12
* Unwrap nested object properties into parent object
13
* @param enabled Whether to enable unwrapping (default: true)
14
* @param prefix Prefix to add to unwrapped property names
15
* @param suffix Suffix to add to unwrapped property names
16
*/
17
@JsonUnwrapped(boolean enabled = true,
18
String prefix = "",
19
String suffix = "")
20
public @interface JsonUnwrapped;
21
```
22
23
**Usage Examples:**
24
25
```java
26
public class Person {
27
private String firstName;
28
private String lastName;
29
30
@JsonUnwrapped
31
private Address address;
32
33
@JsonUnwrapped(prefix = "work_")
34
private Address workAddress;
35
}
36
37
public class Address {
38
private String street;
39
private String city;
40
private String zipCode;
41
}
42
43
// Without @JsonUnwrapped:
44
// {"firstName": "John", "lastName": "Doe", "address": {"street": "123 Main St", "city": "NYC", "zipCode": "10001"}}
45
46
// With @JsonUnwrapped:
47
// {"firstName": "John", "lastName": "Doe", "street": "123 Main St", "city": "NYC", "zipCode": "10001", "work_street": "456 Office Blvd", "work_city": "NYC", "work_zipCode": "10002"}
48
```
49
50
### JsonValue
51
52
Use a single method or field value as the JSON representation of the entire object.
53
54
```java { .api }
55
/**
56
* Use single value as JSON representation of entire object
57
* @param value Whether to use this as the JSON value (default: true)
58
*/
59
@JsonValue(boolean value = true)
60
public @interface JsonValue;
61
```
62
63
**Usage Examples:**
64
65
```java
66
public enum Status {
67
ACTIVE("active"),
68
INACTIVE("inactive"),
69
PENDING("pending");
70
71
private final String code;
72
73
Status(String code) {
74
this.code = code;
75
}
76
77
@JsonValue
78
public String getCode() {
79
return code;
80
}
81
}
82
83
// Serializes as: "active" instead of {"code": "active"}
84
85
public class Money {
86
private final BigDecimal amount;
87
private final String currency;
88
89
public Money(BigDecimal amount, String currency) {
90
this.amount = amount;
91
this.currency = currency;
92
}
93
94
@JsonValue
95
public String toString() {
96
return amount + " " + currency;
97
}
98
}
99
100
// Serializes as: "100.50 USD" instead of {"amount": 100.50, "currency": "USD"}
101
```
102
103
### JsonRawValue
104
105
Serialize string value as raw JSON without quotes or escaping.
106
107
```java { .api }
108
/**
109
* Serialize string value as raw JSON without quotes/escaping
110
* @param value Whether to use raw value serialization (default: true)
111
*/
112
@JsonRawValue(boolean value = true)
113
public @interface JsonRawValue;
114
```
115
116
**Usage Examples:**
117
118
```java
119
public class JsonContainer {
120
private String name;
121
122
@JsonRawValue
123
private String jsonData;
124
125
@JsonRawValue
126
private String configJson;
127
}
128
129
// If jsonData contains: {"settings": {"theme": "dark"}, "count": 42}
130
// Result: {"name": "container", "jsonData": {"settings": {"theme": "dark"}, "count": 42}, "configJson": {...}}
131
// Instead of: {"name": "container", "jsonData": "{\"settings\": {\"theme\": \"dark\"}, \"count\": 42}"}
132
```
133
134
### JsonKey
135
136
Use property value as Map key during serialization.
137
138
```java { .api }
139
/**
140
* Use property value as Map key during serialization
141
* @param value Whether to use this property as key (default: true)
142
*/
143
@JsonKey(boolean value = true)
144
public @interface JsonKey;
145
```
146
147
**Usage Examples:**
148
149
```java
150
public class KeyedItem {
151
@JsonKey
152
private String identifier;
153
154
private String name;
155
private String description;
156
}
157
158
// When used in a collection context, the identifier becomes the map key:
159
// List<KeyedItem> items -> Map<String, KeyedItem> structure
160
// [{"identifier": "item1", "name": "First"}, {"identifier": "item2", "name": "Second"}]
161
// becomes: {"item1": {"name": "First"}, "item2": {"name": "Second"}}
162
```
163
164
### JsonAnyGetter and JsonAnySetter
165
166
Handle dynamic properties with Map-based storage.
167
168
```java { .api }
169
/**
170
* Mark method returning Map to serialize as additional properties
171
* @param enabled Whether to enable any-getter behavior (default: true)
172
*/
173
@JsonAnyGetter(boolean enabled = true)
174
public @interface JsonAnyGetter;
175
176
/**
177
* Mark method/field as fallback for unrecognized properties during deserialization
178
* @param enabled Whether to enable any-setter behavior (default: true)
179
*/
180
@JsonAnySetter(boolean enabled = true)
181
public @interface JsonAnySetter;
182
```
183
184
**Usage Examples:**
185
186
```java
187
public class FlexibleObject {
188
private String name;
189
private String type;
190
191
private Map<String, Object> additionalProperties = new HashMap<>();
192
193
@JsonAnyGetter
194
public Map<String, Object> getAdditionalProperties() {
195
return additionalProperties;
196
}
197
198
@JsonAnySetter
199
public void setAdditionalProperty(String key, Object value) {
200
additionalProperties.put(key, value);
201
}
202
}
203
204
// JSON: {"name": "test", "type": "sample", "customField": "value", "count": 42}
205
// additionalProperties will contain: {"customField": "value", "count": 42}
206
// Serialization includes all additional properties at the top level
207
```
208
209
## Advanced Structure Patterns
210
211
### Nested Unwrapping
212
213
```java
214
public class Employee {
215
private String employeeId;
216
217
@JsonUnwrapped
218
private PersonalInfo personal;
219
220
@JsonUnwrapped(prefix = "contact_")
221
private ContactInfo contact;
222
223
@JsonUnwrapped(prefix = "work_", suffix = "_info")
224
private WorkInfo work;
225
}
226
227
public class PersonalInfo {
228
private String firstName;
229
private String lastName;
230
private LocalDate birthDate;
231
}
232
233
public class ContactInfo {
234
private String email;
235
private String phone;
236
}
237
238
public class WorkInfo {
239
private String department;
240
private String position;
241
}
242
243
// Result: {
244
// "employeeId": "E123",
245
// "firstName": "John", "lastName": "Doe", "birthDate": "1990-01-15",
246
// "contact_email": "john@company.com", "contact_phone": "555-0123",
247
// "work_department_info": "Engineering", "work_position_info": "Senior Developer"
248
// }
249
```
250
251
### Conditional Raw Values
252
253
```java
254
public class ConfigurableResponse {
255
private String status;
256
private String message;
257
258
@JsonRawValue
259
@JsonInclude(JsonInclude.Include.NON_NULL)
260
private String rawData; // Only included if not null, and as raw JSON
261
262
@JsonRawValue
263
private String getFormattedConfig() {
264
// Method can return formatted JSON string
265
return configService.getJsonConfig();
266
}
267
}
268
```
269
270
### Complex Value Objects
271
272
```java
273
public class Coordinate {
274
private final double x;
275
private final double y;
276
277
@JsonCreator
278
public Coordinate(@JsonProperty("x") double x, @JsonProperty("y") double y) {
279
this.x = x;
280
this.y = y;
281
}
282
283
@JsonValue
284
public String toWktString() {
285
return String.format("POINT(%f %f)", x, y);
286
}
287
288
// Custom deserializer would be needed to parse the WKT string back
289
}
290
291
// Serializes as: "POINT(10.500000 20.300000)"
292
```
293
294
### Dynamic Property Handling
295
296
```java
297
public class ApiResource {
298
private String id;
299
private String type;
300
301
// Store metadata separately
302
@JsonIgnore
303
private Map<String, Object> metadata = new HashMap<>();
304
305
// Store dynamic fields separately
306
@JsonIgnore
307
private Map<String, Object> dynamicFields = new HashMap<>();
308
309
@JsonAnyGetter
310
public Map<String, Object> getDynamicFields() {
311
Map<String, Object> all = new HashMap<>(metadata);
312
all.putAll(dynamicFields);
313
return all;
314
}
315
316
@JsonAnySetter
317
public void setDynamicField(String key, Object value) {
318
if (key.startsWith("meta_")) {
319
metadata.put(key.substring(5), value);
320
} else {
321
dynamicFields.put(key, value);
322
}
323
}
324
}
325
```
326
327
### Wrapper and Container Patterns
328
329
```java
330
public class ApiResponse<T> {
331
private String status;
332
private String message;
333
334
@JsonUnwrapped
335
private T data; // Unwrap the actual data into the response
336
337
private Map<String, Object> meta;
338
}
339
340
// Usage:
341
ApiResponse<User> response = new ApiResponse<>();
342
response.setData(new User("john", "john@email.com"));
343
344
// Result: {"status": "success", "message": "OK", "username": "john", "email": "john@email.com", "meta": {...}}
345
```