Skip to content
Merged
20 changes: 19 additions & 1 deletion msgpack-jackson/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,27 @@ When you want to use non-String value as a key of Map, use `MessagePackKeySerial
System.out.println(mapper.readValue(converted, Pojo.class)); // => Pojo{value=1234567890.98765432100}
```

### Serialize and deserialize Instant instances as MessagePack extension type

`timestamp` extension type is defined in MessagePack as type:-1. Registering `TimestampExtensionModule.INSTANCE` module enables automatic serialization and deserialization of java.time.Instant to/from the MessagePack extension type.

```java
ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory())
.registerModule(TimestampExtensionModule.INSTANCE);
Pojo pojo = new Pojo();
// The type of `timestamp` variable is Instant
pojo.timestamp = Instant.now();
byte[] bytes = objectMapper.writeValueAsBytes(pojo);

// The Instant instance is serialized as MessagePack extension type (type: -1)

Pojo deserialized = objectMapper.readValue(bytes, Pojo.class);
System.out.println(deserialized); // "2022-09-14T08:47:24.922Z"
```

### Deserialize extension types with ExtensionTypeCustomDeserializers

`ExtensionTypeCustomDeserializers` helps you to deserialize extension types easily.
`ExtensionTypeCustomDeserializers` helps you to deserialize your own custom extension types easily.

#### Deserialize extension type value directly

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.msgpack.jackson.dataformat;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.msgpack.core.ExtensionTypeHeader;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessagePacker;
import org.msgpack.core.MessageUnpacker;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;

public class TimestampExtensionModule
{
public static final byte EXT_TYPE = -1;
public static final SimpleModule INSTANCE = new SimpleModule("msgpack-ext-timestamp");

static {
INSTANCE.addSerializer(Instant.class, new InstantSerializer(Instant.class));
INSTANCE.addDeserializer(Instant.class, new InstantDeserializer(Instant.class));
}

private static class InstantSerializer extends StdSerializer<Instant>
{
protected InstantSerializer(Class<Instant> t)
{
super(t);
}

@Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
ByteArrayOutputStream os = new ByteArrayOutputStream();
// MEMO: Reusing these MessagePacker and MessageUnpacker instances would improve the performance
try (MessagePacker packer = MessagePack.newDefaultPacker(os)) {
packer.packTimestamp(value);
}
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(os.toByteArray())) {
ExtensionTypeHeader header = unpacker.unpackExtensionTypeHeader();
byte[] bytes = unpacker.readPayload(header.getLength());

MessagePackExtensionType extensionType = new MessagePackExtensionType(EXT_TYPE, bytes);
gen.writeObject(extensionType);
}
}
}

private static class InstantDeserializer extends StdDeserializer<Instant>
{
protected InstantDeserializer(Class<?> vc)
{
super(vc);
}

@Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
MessagePackExtensionType ext = p.readValueAs(MessagePackExtensionType.class);
if (ext.getType() != EXT_TYPE) {
throw new RuntimeException(
String.format("Unexpected extension type (0x%X) for Instant object", ext.getType()));
}

// MEMO: Reusing this MessageUnpacker instance would improve the performance
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(ext.getData())) {
return unpacker.unpackTimestamp(new ExtensionTypeHeader(EXT_TYPE, ext.getData().length));
}
}
}

private TimestampExtensionModule()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
//
// MessagePack for Java
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package org.msgpack.jackson.dataformat;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessagePacker;
import org.msgpack.core.MessageUnpacker;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;

import static org.junit.Assert.assertEquals;

public class TimestampExtensionModuleTest
{
private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
private final SingleInstant singleInstant = new SingleInstant();
private final TripleInstants tripleInstants = new TripleInstants();

private static class SingleInstant
{
public Instant instant;
}

private static class TripleInstants
{
public Instant a;
public Instant b;
public Instant c;
}

@Before
public void setUp()
throws Exception
{
objectMapper.registerModule(TimestampExtensionModule.INSTANCE);
}

@Test
public void testSingleInstantPojo()
throws IOException
{
singleInstant.instant = Instant.now();
byte[] bytes = objectMapper.writeValueAsBytes(singleInstant);
SingleInstant deserialized = objectMapper.readValue(bytes, SingleInstant.class);
assertEquals(singleInstant.instant, deserialized.instant);
}

@Test
public void testTripleInstantsPojo()
throws IOException
{
Instant now = Instant.now();
tripleInstants.a = now.minusSeconds(1);
tripleInstants.b = now;
tripleInstants.c = now.plusSeconds(1);
byte[] bytes = objectMapper.writeValueAsBytes(tripleInstants);
TripleInstants deserialized = objectMapper.readValue(bytes, TripleInstants.class);
assertEquals(now.minusSeconds(1), deserialized.a);
assertEquals(now, deserialized.b);
assertEquals(now.plusSeconds(1), deserialized.c);
}

@Test
public void serialize32BitFormat()
throws IOException
{
singleInstant.instant = Instant.ofEpochSecond(Instant.now().getEpochSecond());

byte[] bytes = objectMapper.writeValueAsBytes(singleInstant);

// Check the size of serialized data first
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
assertEquals("instant", unpacker.unpackString());
assertEquals(4, unpacker.unpackExtensionTypeHeader().getLength());
}

try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
unpacker.unpackString();
assertEquals(singleInstant.instant, unpacker.unpackTimestamp());
}
}

@Test
public void serialize64BitFormat()
throws IOException
{
singleInstant.instant = Instant.ofEpochSecond(Instant.now().getEpochSecond(), 1234);

byte[] bytes = objectMapper.writeValueAsBytes(singleInstant);

// Check the size of serialized data first
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
assertEquals("instant", unpacker.unpackString());
assertEquals(8, unpacker.unpackExtensionTypeHeader().getLength());
}

try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
unpacker.unpackString();
assertEquals(singleInstant.instant, unpacker.unpackTimestamp());
}
}

@Test
public void serialize96BitFormat()
throws IOException
{
singleInstant.instant = Instant.ofEpochSecond(19880866800L /* 2600-01-01 */, 1234);

byte[] bytes = objectMapper.writeValueAsBytes(singleInstant);

// Check the size of serialized data first
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
assertEquals("instant", unpacker.unpackString());
assertEquals(12, unpacker.unpackExtensionTypeHeader().getLength());
}

try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
unpacker.unpackString();
assertEquals(singleInstant.instant, unpacker.unpackTimestamp());
}
}

@Test
public void deserialize32BitFormat()
throws IOException
{
Instant instant = Instant.ofEpochSecond(Instant.now().getEpochSecond());

ByteArrayOutputStream os = new ByteArrayOutputStream();
try (MessagePacker packer = MessagePack.newDefaultPacker(os)) {
packer.packMapHeader(1)
.packString("instant")
.packTimestamp(instant);
}

byte[] bytes = os.toByteArray();
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
unpacker.unpackString();
assertEquals(4, unpacker.unpackExtensionTypeHeader().getLength());
}

SingleInstant deserialized = objectMapper.readValue(bytes, SingleInstant.class);
assertEquals(instant, deserialized.instant);
}

@Test
public void deserialize64BitFormat()
throws IOException
{
Instant instant = Instant.ofEpochSecond(Instant.now().getEpochSecond(), 1234);

ByteArrayOutputStream os = new ByteArrayOutputStream();
try (MessagePacker packer = MessagePack.newDefaultPacker(os)) {
packer.packMapHeader(1)
.packString("instant")
.packTimestamp(instant);
}

byte[] bytes = os.toByteArray();
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
unpacker.unpackString();
assertEquals(8, unpacker.unpackExtensionTypeHeader().getLength());
}

SingleInstant deserialized = objectMapper.readValue(bytes, SingleInstant.class);
assertEquals(instant, deserialized.instant);
}

@Test
public void deserialize96BitFormat()
throws IOException
{
Instant instant = Instant.ofEpochSecond(19880866800L /* 2600-01-01 */, 1234);

ByteArrayOutputStream os = new ByteArrayOutputStream();
try (MessagePacker packer = MessagePack.newDefaultPacker(os)) {
packer.packMapHeader(1)
.packString("instant")
.packTimestamp(instant);
}

byte[] bytes = os.toByteArray();
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes)) {
unpacker.unpackMapHeader();
unpacker.unpackString();
assertEquals(12, unpacker.unpackExtensionTypeHeader().getLength());
}

SingleInstant deserialized = objectMapper.readValue(bytes, SingleInstant.class);
assertEquals(instant, deserialized.instant);
}
}