Add msgpack-jackson3 module#983
Draft
komamitsu wants to merge 4 commits into
Draft
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new msgpack-jackson3 submodule intended to provide MessagePack <-> Jackson integration for Jackson 3.x (tools.jackson.*) while keeping the existing Jackson 2.x module intact.
Changes:
- Adds a new
msgpack-jackson3sbt subproject targeting Java 17 and Jacksontools.jackson.core:jackson-databind:3.1.2. - Ports the MessagePack
TokenStreamFactory,JsonParser,JsonGenerator, and mapper/key-serialization glue to the Jackson 3 API surface. - Adds a full test suite (plus benchmark tests) for parser/generator/factory/mapper behavior under the new module.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| build.sbt | Adds msgpack-jackson3 subproject definition and aggregates it under root. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/ExtensionTypeCustomDeserializers.java | Extension-type custom deserializer registry for parser embedded objects. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/JsonArrayFormat.java | Jackson annotation introspector to serialize POJOs as arrays (schema-less). |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java | POJO + serializer for MessagePack extension types. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java | Jackson TokenStreamFactory implementation for MessagePack. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java | Jackson JsonGenerator implementation that writes MessagePack. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackKeySerializer.java | Key serializer that routes non-String map keys through MessagePack key handling. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackMapper.java | ObjectMapper wrapper + builder helpers for BigInteger/BigDecimal formatting. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java | Jackson JsonParser implementation that reads MessagePack. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackReadContext.java | Read context implementation for tracking nesting/name/dup detection. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackSerializedString.java | SerializableString wrapper used for MessagePack key serialization. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackSerializerFactory.java | Serializer factory override to enforce MessagePack key serialization. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/TimestampExtensionModule.java | Jackson module for MessagePack timestamp extension <-> Instant. |
| msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/Tuple.java | Simple tuple used for cached unpacker bookkeeping. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/ExampleOfTypeInformationSerDe.java | Example test for embedding type metadata for polymorphic objects. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForFieldIdTest.java | Tests for integer-key support and mixed key handling. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java | POJO round-trip tests (normal, nested, schema-less array format, etc.). |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java | Shared test fixtures and helper POJOs. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackFactoryTest.java | Factory copy/creation tests. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java | Generator behavioral tests (objects/arrays/primitives/keys/big numbers/etc.). |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackMapperTest.java | Mapper builder behavior tests for BigInteger/BigDecimal-as-string options. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackParserTest.java | Parser behavioral tests for objects/arrays/primitives/extensions/errors. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/TimestampExtensionModuleTest.java | Tests for MessagePack timestamp extension serialization/deserialization. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/benchmark/Benchmarker.java | Benchmark harness used by benchmark tests. |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatHugeDataBenchmarkTest.java | Benchmark test for huge data (JSON vs MessagePack). |
| msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java | Benchmark test for POJO ser/de (JSON vs MessagePack). |
Comments suppressed due to low confidence (2)
msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java:501
getBytesIfAsciiwrites intobytes[i]while iteratingifromoffsettooffset+len. Whenoffset != 0this will write past the end of the newly allocatedbytesarray (lengthlen) and can throwArrayIndexOutOfBoundsException. Index into the destination array relative tooffset(or use a separate destination index).
private byte[] getBytesIfAscii(char[] chars, int offset, int len)
{
byte[] bytes = new byte[len];
for (int i = offset; i < offset + len; i++) {
char c = chars[i];
if (c >= 0x80) {
return null;
}
bytes[i] = (byte) c;
}
return bytes;
msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java:531
writeByteArrayTextValueignores the providedoffset/lenwhen the bytes are ASCII: it wraps the entiretextarray inAsciiCharString. This can serialize extra bytes outside the intended slice. Consider copying the[offset, offset+len)range (or store the slice metadata) when creatingAsciiCharString.
private void writeByteArrayTextValue(byte[] text, int offset, int len) throws IOException
{
if (areAllAsciiBytes(text, offset, len)) {
addValueNode(new AsciiCharString(text));
return;
}
addValueNode(new String(text, offset, len, DEFAULT_CHARSET));
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+388
to
+400
| else if (v instanceof ByteBuffer) { | ||
| ByteBuffer bb = (ByteBuffer) v; | ||
| int len = bb.remaining(); | ||
| if (bb.hasArray()) { | ||
| messagePacker.packBinaryHeader(len); | ||
| messagePacker.writePayload(bb.array(), bb.arrayOffset(), len); | ||
| } | ||
| else { | ||
| byte[] data = new byte[len]; | ||
| bb.get(data); | ||
| messagePacker.packBinaryHeader(len); | ||
| messagePacker.addPayload(data); | ||
| } |
Comment on lines
+141
to
+151
| private String unpackString(MessageUnpacker messageUnpacker) throws IOException | ||
| { | ||
| int strLen = messageUnpacker.unpackRawStringHeader(); | ||
| if (strLen <= tempBytes.length) { | ||
| messageUnpacker.readPayload(tempBytes, 0, strLen); | ||
| return new String(tempBytes, 0, strLen); | ||
| } | ||
| else { | ||
| byte[] bytes = messageUnpacker.readPayload(strLen); | ||
| return new String(bytes, 0, strLen, StandardCharsets.UTF_8); | ||
| } |
Comment on lines
+18
to
+24
| import com.fasterxml.jackson.annotation.JsonFormat; | ||
|
|
||
| import tools.jackson.core.Version; | ||
| import tools.jackson.databind.ObjectMapper; | ||
| import tools.jackson.databind.cfg.MapperBuilder; | ||
| import tools.jackson.databind.cfg.MapperBuilderState; | ||
|
|
Comment on lines
+6
to
+10
|
|
||
| import com.fasterxml.jackson.annotation.JsonFormat; | ||
|
|
||
| import static com.fasterxml.jackson.annotation.JsonFormat.Shape.ARRAY; | ||
|
|
Comment on lines
+69
to
+111
| return 0; | ||
| } | ||
|
|
||
| @Override | ||
| public int appendQuoted(char[] chars, int i) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| @Override | ||
| public int appendUnquotedUTF8(byte[] bytes, int i) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| @Override | ||
| public int appendUnquoted(char[] chars, int i) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| @Override | ||
| public int writeQuotedUTF8(OutputStream outputStream) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| @Override | ||
| public int writeUnquotedUTF8(OutputStream outputStream) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| @Override | ||
| public int putQuotedUTF8(ByteBuffer byteBuffer) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| @Override | ||
| public int putUnquotedUTF8(ByteBuffer byteBuffer) | ||
| { | ||
| return 0; |
| 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())); |
Comment on lines
+18
to
+20
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; |
Adds a new msgpack-jackson3 module providing Jackson 3.x (tools.jackson.*)
integration for MessagePack serialization, requiring Java 17+.
New module structure:
- MessagePackFactory / MessagePackMapper / MessagePackFactoryBuilder
- MessagePackGenerator: deferred-flush design (buffers a node tree, emits
MessagePack headers with correct sizes on flush/close)
- MessagePackParser: streaming MessagePack deserialization
- MessagePackSerializedString: SerializableString for non-string map keys
- MessagePackExtensionType: extension type support
- TimestampExtensionModule: Instant serialization via msgpack-core timestamps
- JMH benchmarks (src/jmh/java) replacing hand-rolled System.nanoTime tests
Key correctness fixes vs. initial draft:
- Generator contract: streamWriteContext() returns live SimpleStreamWriteContext;
addValueNode calls writeContext.writeValue(); writeName/writePropertyId check
writeContext.writeName() return; writeStartArray/Object call _verifyValueWrite
- writePropertyId (integer keys): calls writeContext.writeName() to keep Jackson
context state in sync alongside the internal node state
- writeString(Reader, int): chunked reading (8192-char buffer) for len>=0 path
to avoid unbounded upfront allocation
- MessagePackSerializedString: asQuotedUTF8 returns unquoted bytes ("quoted"
means escaped, not surrounded by quotes); all append/write/put methods
implemented correctly
- TimestampExtensionModule: IOException wrapped as UncheckedIOException
- MessagePackFactory.snapshot(): delegates to copy() instead of returning this
- isClosed(): fixed by delegating close() lifecycle to super.close()
- ByteBuffer serialization: uses bb.arrayOffset() + bb.position() to honour
non-zero position
- writeString(char[], offset, len) / writeUTF8String(byte[], offset, len):
offset bugs fixed
- Stale currentState after container close: reset to IN_ROOT in endCurrentContainer
Build/CI:
- msgpack-jackson3 excluded from root aggregate on Java < 17; CI matrix skips
the module for older JDK entries
- sbt-jmh plugin added for JMH benchmark support
- OSGi manifest cleaned up; dead code removed
5e3ef27 to
504e02b
Compare
- MessagePackParser: make inner constructor package-private so MessagePackFactory can use it directly - MessagePackFactory._createParser(byte[],int,int): replace Arrays.copyOfRange with ArrayBufferInput(data, offset, len) to avoid an unnecessary copy when parsing a byte-array slice - MessagePackSerializedString: add capacity checks to all append/put methods (return -1 instead of throwing on overflow) and propagate IOException from writeQuotedUTF8/writeUnquotedUTF8 - TimestampExtensionModule.InstantDeserializer: replace bare RuntimeException with ctxt.reportInputMismatch for proper Jackson error reporting - plans/preexisting-issues.md: document node-buffering architectural constraint and nested-POJO generator inefficiency as known FYI items
1b49f14 to
4164438
Compare
…ove unused byte[] constructor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
msgpack-jackson3submodule (jackson-dataformat-msgpack3) that ports the existing Jackson 2.x integration to Jackson 3.x (tools.jacksonnamespace)com.github.sbt:junit-interface— no extra sbt plugins neededKey differences from
msgpack-jacksoncom.fasterxml.jackson→tools.jacksonTokenStreamLocationreplacesJsonLocation; byte offset passed ascolumnNr(truncates for inputs > 2 GB, documented with comment)JavaInfo/ char-array optimization removed — always false on Java 17+ sinceString.valueisbyte[]importPackageexcludesandroid.osandsun.*matchingmsgpack-corejavacOptionsset to--source 17 --target 17Test plan
./sbt "msgpack-jackson3/test"— 77 tests pass./sbt "msgpack-jackson3/test:compile"— no checkstyle errorsmsgpack-coreandmsgpack-jacksontests unaffected