Skip to content

Commit a6b6c32

Browse files
fix(decode): preserve mapKeyConverter in Decoder#clone()
Decoder#clone() is used for re-entrant decoding (e.g. an extension codec that decodes nested MessagePack on the same Decoder instance, issue #195). It copied every decoder option except mapKeyConverter, so any map decoded re-entrantly silently fell back to the default key converter instead of the one the caller configured. Copy mapKeyConverter in clone() alongside the other options, and add a regression test that decodes a nested map through an extension codec.
1 parent 6d4b666 commit a6b6c32

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

src/Decoder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ export class Decoder<ContextType = undefined> {
264264
maxMapLength: this.maxMapLength,
265265
maxExtLength: this.maxExtLength,
266266
keyDecoder: this.keyDecoder,
267+
mapKeyConverter: this.mapKeyConverter,
267268
} as any);
268269
}
269270

test/reuse-instances-with-extensions.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,31 @@ describe("reuse instances with extensions", () => {
4545
const data = context.decode(buf);
4646
deepStrictEqual(data, [BigInt(1), BigInt(2), BigInt(3)]);
4747
});
48+
49+
it("keeps mapKeyConverter for maps decoded re-entrantly inside an extension", () => {
50+
const MSGPACK_EXT_TYPE_WRAP = 1;
51+
const extensionCodec = new ExtensionCodec();
52+
const encoder = new Encoder({ extensionCodec });
53+
const decoder = new Decoder({ extensionCodec, mapKeyConverter: (key) => String(key).toUpperCase() });
54+
55+
class Wrapped {
56+
readonly inner: unknown;
57+
constructor(inner: unknown) {
58+
this.inner = inner;
59+
}
60+
}
61+
extensionCodec.register({
62+
type: MSGPACK_EXT_TYPE_WRAP,
63+
encode: (value) => (value instanceof Wrapped ? encoder.encode(value.inner) : null),
64+
// decode re-enters the same decoder instance, which triggers Decoder#clone()
65+
decode: (data) => new Wrapped(decoder.decode(data)),
66+
});
67+
68+
const buf = encoder.encode({ a: 1, nested: new Wrapped({ b: 2 }) });
69+
const decoded = decoder.decode(buf) as Record<string, Wrapped>;
70+
71+
// The nested map is decoded on the cloned decoder; it must use the same converter.
72+
deepStrictEqual(Object.keys(decoded), ["A", "NESTED"]);
73+
deepStrictEqual(decoded["NESTED"]!.inner, { B: 2 });
74+
});
4875
});

0 commit comments

Comments
 (0)