0
# Serialization
1
2
JSON-based object serialization with compression and custom ObjectMapper support. The serialization system handles converting Java objects to byte arrays for storage and retrieval.
3
4
## Capabilities
5
6
### KVStoreSerializer
7
8
Core serialization class that handles converting objects to and from byte arrays using Jackson JSON serialization with GZIP compression.
9
10
```java { .api }
11
/**
12
* Serializer used to translate between app-defined types and the LevelDB store.
13
*
14
* The serializer is based on Jackson, so values are written as JSON. It also allows "naked strings"
15
* and integers to be written as values directly, which will be written as UTF-8 strings.
16
*/
17
public class KVStoreSerializer {
18
/**
19
* Object mapper used to process app-specific types. If an application requires a specific
20
* configuration of the mapper, it can subclass this serializer and add custom configuration
21
* to this object.
22
*/
23
protected final ObjectMapper mapper;
24
25
/**
26
* Creates a new serializer with default Jackson ObjectMapper configuration.
27
*/
28
public KVStoreSerializer();
29
}
30
```
31
32
**Default Behavior:**
33
- Uses Jackson ObjectMapper for JSON serialization
34
- Applies GZIP compression to reduce storage size
35
- Special handling for String values (stored as UTF-8 without compression)
36
- Automatic data compression for space efficiency
37
38
### Object Serialization
39
40
Convert Java objects to byte arrays for storage, with automatic compression for non-string values.
41
42
```java { .api }
43
/**
44
* Serialize an object to bytes for storage.
45
* @param o - The object to serialize. Strings are stored as UTF-8, other objects as compressed JSON.
46
* @return Byte array representation of the object
47
* @throws Exception If serialization fails
48
*/
49
public final byte[] serialize(Object o) throws Exception;
50
```
51
52
**Usage Examples:**
53
54
```java
55
KVStoreSerializer serializer = new KVStoreSerializer();
56
57
// Serialize a simple string (stored as UTF-8, no compression)
58
String text = "Hello, World!";
59
byte[] stringBytes = serializer.serialize(text);
60
61
// Serialize a complex object (stored as compressed JSON)
62
Person person = new Person("p1", "Alice", 30);
63
byte[] personBytes = serializer.serialize(person);
64
65
// Serialize numbers and primitives
66
Integer number = 42;
67
byte[] numberBytes = serializer.serialize(number);
68
69
// Serialize collections
70
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
71
byte[] listBytes = serializer.serialize(names);
72
73
// Serialize maps
74
Map<String, Integer> scores = new HashMap<>();
75
scores.put("Alice", 95);
76
scores.put("Bob", 87);
77
byte[] mapBytes = serializer.serialize(scores);
78
```
79
80
### Object Deserialization
81
82
Convert byte arrays back to Java objects, with automatic decompression and type handling.
83
84
```java { .api }
85
/**
86
* Deserialize bytes back to an object of the specified type.
87
* @param data - The byte array to deserialize
88
* @param klass - The target class type
89
* @return The deserialized object
90
* @throws Exception If deserialization fails
91
*/
92
public final <T> T deserialize(byte[] data, Class<T> klass) throws Exception;
93
```
94
95
**Usage Examples:**
96
97
```java
98
KVStoreSerializer serializer = new KVStoreSerializer();
99
100
// Deserialize string
101
byte[] stringBytes = /* ... */;
102
String text = serializer.deserialize(stringBytes, String.class);
103
104
// Deserialize complex object
105
byte[] personBytes = /* ... */;
106
Person person = serializer.deserialize(personBytes, Person.class);
107
108
// Deserialize numbers
109
byte[] numberBytes = /* ... */;
110
Integer number = serializer.deserialize(numberBytes, Integer.class);
111
112
// Deserialize collections (requires generic type handling)
113
byte[] listBytes = /* ... */;
114
// Note: For generic collections, you may need TypeReference with Jackson
115
List<String> names = serializer.deserialize(listBytes, List.class);
116
117
// Deserialize maps
118
byte[] mapBytes = /* ... */;
119
Map<String, Integer> scores = serializer.deserialize(mapBytes, Map.class);
120
```
121
122
### Long Value Serialization
123
124
Special handling for long values used internally by the store for counters and metadata.
125
126
```java { .api }
127
/**
128
* Serialize a long value to bytes.
129
* @param value - The long value to serialize
130
* @return Byte array representation of the long
131
*/
132
final byte[] serialize(long value);
133
134
/**
135
* Deserialize bytes back to a long value.
136
* @param data - The byte array to deserialize
137
* @return The long value
138
*/
139
final long deserializeLong(byte[] data);
140
```
141
142
**Usage Examples:**
143
144
```java
145
KVStoreSerializer serializer = new KVStoreSerializer();
146
147
// Serialize long values (used internally for counters)
148
long counter = 12345L;
149
byte[] counterBytes = serializer.serialize(counter);
150
151
// Deserialize long values
152
long retrievedCounter = serializer.deserializeLong(counterBytes);
153
System.out.println("Counter: " + retrievedCounter);
154
```
155
156
### Custom Serializer Configuration
157
158
Create custom serializers by subclassing KVStoreSerializer to configure Jackson ObjectMapper behavior.
159
160
**Usage Examples:**
161
162
```java
163
// Custom serializer with specific Jackson configuration
164
public class CustomKVStoreSerializer extends KVStoreSerializer {
165
public CustomKVStoreSerializer() {
166
super();
167
// Configure the mapper for custom behavior
168
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
169
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
170
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
171
172
// Add custom modules
173
mapper.registerModule(new JavaTimeModule());
174
175
// Configure property naming strategy
176
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
177
}
178
}
179
180
// Use custom serializer with LevelDB
181
KVStoreSerializer customSerializer = new CustomKVStoreSerializer();
182
KVStore store = new LevelDB(new File("./custom-store"), customSerializer);
183
184
// Objects will be serialized using custom configuration
185
Person person = new Person("p1", "Alice", 30);
186
person.birthDate = LocalDateTime.now();
187
store.write(person); // Uses custom date formatting and snake_case properties
188
```
189
190
### Jackson Annotations Support
191
192
The serializer supports all Jackson annotations for controlling serialization behavior.
193
194
**Usage Examples:**
195
196
```java
197
import com.fasterxml.jackson.annotation.*;
198
199
public class AnnotatedPerson {
200
@KVIndex
201
@JsonProperty("person_id") // Custom JSON property name
202
public String id;
203
204
@KVIndex("name")
205
@JsonProperty("full_name")
206
public String name;
207
208
@KVIndex("age")
209
public int age;
210
211
@JsonIgnore // Exclude from serialization
212
public String internalNotes;
213
214
@JsonFormat(pattern = "yyyy-MM-dd") // Custom date format
215
public Date birthDate;
216
217
@JsonInclude(JsonInclude.Include.NON_NULL) // Only include if not null
218
public String nickname;
219
220
// Jackson will use this constructor for deserialization
221
@JsonCreator
222
public AnnotatedPerson(
223
@JsonProperty("person_id") String id,
224
@JsonProperty("full_name") String name,
225
@JsonProperty("age") int age) {
226
this.id = id;
227
this.name = name;
228
this.age = age;
229
}
230
231
// Getters and setters...
232
}
233
234
// The serializer will respect all Jackson annotations
235
KVStore store = new InMemoryStore();
236
AnnotatedPerson person = new AnnotatedPerson("p1", "Alice Johnson", 30);
237
person.nickname = "Al";
238
person.internalNotes = "VIP customer"; // Will be ignored during serialization
239
240
store.write(person); // Serialized using Jackson annotations
241
AnnotatedPerson retrieved = store.read(AnnotatedPerson.class, "p1");
242
// retrieved.internalNotes will be null (was ignored)
243
```
244
245
### Error Handling
246
247
Handle serialization and deserialization errors appropriately.
248
249
**Usage Examples:**
250
251
```java
252
KVStoreSerializer serializer = new KVStoreSerializer();
253
254
try {
255
// Attempt to serialize an object
256
ComplexObject obj = new ComplexObject();
257
byte[] data = serializer.serialize(obj);
258
259
// Attempt to deserialize
260
ComplexObject retrieved = serializer.deserialize(data, ComplexObject.class);
261
262
} catch (JsonProcessingException e) {
263
// Jackson serialization/deserialization error
264
System.err.println("JSON processing failed: " + e.getMessage());
265
} catch (IOException e) {
266
// GZIP compression/decompression error
267
System.err.println("Compression failed: " + e.getMessage());
268
} catch (Exception e) {
269
// Other serialization errors
270
System.err.println("Serialization failed: " + e.getMessage());
271
}
272
```
273
274
### Performance Considerations
275
276
**String vs Object Serialization:**
277
```java
278
KVStoreSerializer serializer = new KVStoreSerializer();
279
280
// Strings are stored as UTF-8 (fast, no compression)
281
String simpleText = "Hello World";
282
byte[] stringData = serializer.serialize(simpleText); // Fast, small overhead
283
284
// Objects are JSON + GZIP compressed (slower, better space efficiency)
285
Person person = new Person("p1", "Alice", 30);
286
byte[] objectData = serializer.serialize(person); // Slower, compressed
287
```
288
289
**Large Object Handling:**
290
```java
291
// For large objects, serialization cost increases
292
LargeDataObject largeObj = new LargeDataObject();
293
largeObj.data = new byte[1024 * 1024]; // 1MB of data
294
295
long start = System.currentTimeMillis();
296
byte[] serialized = serializer.serialize(largeObj);
297
long serializationTime = System.currentTimeMillis() - start;
298
299
System.out.println("Serialization took: " + serializationTime + "ms");
300
System.out.println("Original size: " + largeObj.data.length + " bytes");
301
System.out.println("Compressed size: " + serialized.length + " bytes");
302
System.out.println("Compression ratio: " + (1.0 - (double)serialized.length / largeObj.data.length));
303
```
304
305
### Thread Safety
306
307
The KVStoreSerializer is thread-safe and can be used concurrently.
308
309
**Usage Examples:**
310
311
```java
312
// Single serializer instance can be used by multiple threads
313
KVStoreSerializer sharedSerializer = new KVStoreSerializer();
314
315
// Thread-safe concurrent usage
316
ExecutorService executor = Executors.newFixedThreadPool(10);
317
318
for (int i = 0; i < 100; i++) {
319
final int index = i;
320
executor.submit(() -> {
321
try {
322
Person person = new Person("p" + index, "Name" + index, 20 + index);
323
byte[] data = sharedSerializer.serialize(person); // Thread-safe
324
Person retrieved = sharedSerializer.deserialize(data, Person.class); // Thread-safe
325
} catch (Exception e) {
326
e.printStackTrace();
327
}
328
});
329
}
330
331
executor.shutdown();
332
```