Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialization of record component with @JsonbDateFormat(TIME_IN_MILLIS) fails #651

Open
jacopo-cavallarin opened this issue Oct 11, 2024 · 0 comments
Labels
bug Something isn't working right

Comments

@jacopo-cavallarin
Copy link

jacopo-cavallarin commented Oct 11, 2024

Describe the bug
Using this record as example:

record Example(@JsonbDateFormat(TIME_IN_MILLIS) Instant longInstant, Instant stringInstant) {}

and this JSON to deserialize:

{ "longInstant": 1728574424000, "stringInstant": "2024-10-11T13:28:22Z" }

The deserialization fails with the following exception:

jakarta.json.bind.JsonbException: Internal error: Pattern includes reserved character: '#'
	at org.eclipse.yasson.internal.DeserializationContextImpl.deserializeItem(DeserializationContextImpl.java:142)
	at org.eclipse.yasson.internal.DeserializationContextImpl.deserialize(DeserializationContextImpl.java:127)
	at org.eclipse.yasson.internal.JsonBinding.deserialize(JsonBinding.java:55)
	at org.eclipse.yasson.internal.JsonBinding.fromJson(JsonBinding.java:62)
	...
Caused by: java.lang.IllegalArgumentException: Pattern includes reserved character: '#'
	at java.base/java.time.format.DateTimeFormatterBuilder.parsePattern(DateTimeFormatterBuilder.java:2053)
	at java.base/java.time.format.DateTimeFormatterBuilder.appendPattern(DateTimeFormatterBuilder.java:1907)
	at java.base/java.time.format.DateTimeFormatter.ofPattern(DateTimeFormatter.java:593)
	at org.eclipse.yasson.internal.AnnotationIntrospector.lambda$getConstructorDateFormatter$9(AnnotationIntrospector.java:595)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.eclipse.yasson.internal.AnnotationIntrospector.getConstructorDateFormatter(AnnotationIntrospector.java:595)
	at org.eclipse.yasson.internal.model.CreatorModel.<init>(CreatorModel.java:55)
	at org.eclipse.yasson.internal.AnnotationIntrospector.createJsonbCreator(AnnotationIntrospector.java:212)
	at org.eclipse.yasson.internal.ClassMultiReleaseExtension.findCreator(ClassMultiReleaseExtension.java:55)
	at org.eclipse.yasson.internal.AnnotationIntrospector.getCreator(AnnotationIntrospector.java:189)
	at org.eclipse.yasson.internal.AnnotationIntrospector.introspectCustomization(AnnotationIntrospector.java:798)
	at org.eclipse.yasson.internal.MappingContext.lambda$createParseClassModelFunction$1(MappingContext.java:88)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1713)
	at org.eclipse.yasson.internal.MappingContext.getOrCreateClassModel(MappingContext.java:77)
	at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.deserializerChain(DeserializationModelCreator.java:122)
	at org.eclipse.yasson.internal.DeserializationContextImpl.deserializeItem(DeserializationContextImpl.java:137)
	... 9 more

To Reproduce
Run the following Junit5 test:

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.annotation.JsonbDateFormat;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.Instant;

import static jakarta.json.bind.annotation.JsonbDateFormat.TIME_IN_MILLIS;
import static java.time.temporal.ChronoUnit.MILLIS;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;

class TimeInMillisRecordDeserializationTest {
    public record Example(@JsonbDateFormat(TIME_IN_MILLIS) Instant longInstant, Instant stringInstant) {}

    Jsonb jsonb;

    @BeforeEach
    void setUp() {
        jsonb = JsonbBuilder.create();
    }

    @AfterEach
    void tearDown() throws Exception {
        jsonb.close();
    }

    @Test
    void shouldDeserialize() {
        var expected = new Example(Instant.now().truncatedTo(MILLIS), Instant.now());

        var json = "{ \"longInstant\": %d, \"stringInstant\": \"%s\" }"
                .formatted(expected.longInstant().toEpochMilli(), expected.stringInstant());

        var actual = assertDoesNotThrow(() -> jsonb.fromJson(json, Example.class));

        assertEquals(expected, actual);
    }
}

This test will fail with the exception mentioned above.

Expected behavior
The test passes successfully.

System information:

  • OS: macOS 15.0.1
  • Java Version: 23
  • Yasson Version: 3.0.4

Additional context
Using the annotation on the record level works fine, but forces all datetime components in the record to be deserialized from epoch millis:

@JsonbDateFormat(TIME_IN_MILLIS)
record Example(Instant longInstant, Instant stringInstant) {}

Also, the annotation works fine on POJOs:

class Example {
    @JsonbDateFormat(TIME_IN_MILLIS)
    final Instant longInstant;
    final Instant stringInstant;
    
    @JsonbCreator
    Example(Instant longInstant, Instant stringInstant) { /* ... */ }

    // getters,equals,hashCode...
}
@jacopo-cavallarin jacopo-cavallarin added the bug Something isn't working right label Oct 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working right
Projects
None yet
Development

No branches or pull requests

1 participant