eclipse-ee4j / eclipse-ee4j/yasson

Yasson 3.0.3 - Serialization of a Map fails if the key is of a type implemented as SupportedMapKey and using a csutom Serializer

Open
#663 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
218
Forks
109
Avg merge
1d 5h
Merged PRs (30d)
9

Description

**Describe the bug**
Define a custom Serializer for a `LocalDate` object like the following:

```java
public static class LocalDateSerializer implements JsonbSerializer {

private static final DateTimeFormatter SHORT_FORMAT = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);

@Override
public void serialize(LocalDate obj, JsonGenerator generator, SerializationContext ctx) {
generator.write(SHORT_FORMAT.format(obj));
}
}
```

*Note:* This `LocalDate` is also included in the `TypeSerializers.SUPPORTED_MAP_KEYS` as default serialization implementation.

Now use a Pojo class that contains a `Map` field with `LocalDate` as the key:

```java
public class TestObject {
private Map localDateKeyMap;
...
}
```

If you try to serialize it with current version 3.0.3 like the following:
```java
Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
.withSerializers(new LocalDateSerializer())
.withDeserializers(new LocalDateDeserializer()));
TestObject o = new TestObject();
o.getLocalDateKeyMap().put(LocalDate.now(), "today");
jsonb.toJson(o);
```

The following exception is thrown:

```
jakarta.json.bind.JsonbException: Unable to serialize property 'values' from test.MapObjectTest.MapObjectLocalDateString
at org.eclipse.yasson.internal.serializer.ObjectSerializer.lambda$serialize$0(ObjectSerializer.java:43)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
at org.eclipse.yasson.internal.serializer.ObjectSerializer.serialize(ObjectSerializer.java:38)
at org.eclipse.yasson.internal.serializer.RecursionChecker.serialize(RecursionChecker.java:38)
at org.eclipse.yasson.internal.serializer.KeyWriter.serialize(KeyWriter.java:41)
at org.eclipse.yasson.internal.serializer.NullVisibilitySwitcher.serialize(NullVisibilitySwitcher.java:40)
at org.eclipse.yasson.internal.serializer.NullSerializer.serialize(NullSerializer.java:67)
at org.eclipse.yasson.internal.SerializationContextImpl.serializeObject(SerializationContextImpl.java:197)
at org.eclipse.yasson.internal.SerializationContextImpl.marshall(SerializationContextImpl.java:133)
at org.eclipse.yasson.internal.SerializationContextImpl.marshall(SerializationContextImpl.java:159)
at org.eclipse.yasson.internal.JsonBinding.toJson(JsonBinding.java:131)
at test.MapObjectTest.test1(MapObjectTest.java:102)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:316)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:240)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:214)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:155)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)
Suppressed: jakarta.json.stream.JsonGenerationException: Generating incomplete JSON
at org.eclipse.parsson.JsonGeneratorImpl.close(JsonGeneratorImpl.java:519)
at org.eclipse.yasson.internal.JsonBinding.toJson(JsonBinding.java:130)
... 29 more
Caused by: jakarta.json.stream.JsonGenerationException: Illegal method during JSON generation, not valid in current context IN_OBJECT
at org.eclipse.parsson.JsonGeneratorImpl.checkContextForValue(JsonGeneratorImpl.java:405)
at org.eclipse.parsson.JsonGeneratorImpl.write(JsonGeneratorImpl.java:357)
at org.eclipse.yasson.internal.serializer.YassonGenerator.write(YassonGenerator.java:161)
at test.MapObjectTest$LocalDateSerializer.serialize(MapObjectTest.java:36)
at test.MapObjectTest$LocalDateSerializer.serialize(MapObjectTest.java:30)
at org.eclipse.yasson.internal.serializer.UserDefinedSerializer.serialize(UserDefinedSerializer.java:35)
at org.eclipse.yasson.internal.serializer.RecursionChecker.serialize(RecursionChecker.java:38)
at org.eclipse.yasson.internal.serializer.KeyWriter.serialize(KeyWriter.java:41)
at org.eclipse.yasson.internal.serializer.NullSerializer.serialize(NullSerializer.java:67)
at org.eclipse.yasson.internal.serializer.MapSerializer$StringKeyMapSerializer.lambda$serialize$0(MapSerializer.java:108)
at java.base/java.util.HashMap.forEach(HashMap.java:1337)
at org.eclipse.yasson.internal.serializer.MapSerializer$StringKeyMapSerializer.serialize(MapSerializer.java:107)
at org.eclipse.yasson.internal.serializer.KeyWriter.serialize(KeyWriter.java:41)
at org.eclipse.yasson.internal.serializer.NullVisibilitySwitcher.serialize(NullVisibilitySwitcher.java:40)
at org.eclipse.yasson.internal.serializer.NullSerializer.serialize(NullSerializer.java:67)
at org.eclipse.yasson.internal.serializer.ValueGetterSerializer.serialize(ValueGetterSerializer.java:43)
at org.eclipse.yasson.internal.serializer.ObjectSerializer.lambda$serialize$0(ObjectSerializer.java:41)
... 39 more
```

**To Reproduce**
```shell
unzip issue-map-local-date-key.zip
cd issue-map-local-date-key
mvn clean test
```

**Expected behavior**
The test should pass as with earlier yasson versions and the `LocalDateSerializer` should be used to serialize the `LocalDate` key into a String.

**System information:**
- OS: any
- Java Version: 11, 17
- Yasson Version: 3.0.3

**Additional context**
The same was working in previous yasson versions.

Reproducer project: [issue-map-local-date-key.zip](https://github.com/user-attachments/files/19183573/issue-map-local-date-key.zip)

Contributor guide

Open the contributing guide

Research direction

Start with the attached reproducer and run `mvn clean test`; the failing case is `MapObjectTest.test1` in `MapObjectTest.java`. Trace the interaction among `MapSerializer`, `KeyWriter`, and `UserDefinedSerializer` for a `LocalDate` map key, then verify that the custom serializer produces a string and the test passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.