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

JSON-B date serialization and deserialization problem #24926

Open
black-pwq opened this issue Apr 19, 2024 · 7 comments
Open

JSON-B date serialization and deserialization problem #24926

black-pwq opened this issue Apr 19, 2024 · 7 comments

Comments

@black-pwq
Copy link

Environment Details

  • GlassFish Version (7.0.12):
  • JDK version: 17
  • OS: Windows 10
  • Database: not related

Problem Description

JAX-RS runtime don't bind string like "2024-12-12" to java.util.Date. Here's example that don't work:

    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    @Produces
    @Path("test")
    public String test(Date date) {
        return "great";
    }

ISO 8601 date only string should be supported as stated in JSON-B 3.0:

If not specified otherwise, the date time format for serialization and deserialization is ISO 8601 without offset, as specified in java.time.format.DateTimeFormatter.ISO_DATE

The above example reports:

[2024-04-19T19:12:06.999676+08:00] [GF 7.0.12] [WARNING] [] [jakarta.enterprise.web.vs.server] [tid: _ThreadID=41 _ThreadName=http-listener-1(5)] [levelValue: 900] [[
  StandardWrapperValve[me.blackpwq.uom.rest.UomApplication]: Servlet.service() for servlet me.blackpwq.uom.rest.UomApplication threw exception
java.time.format.DateTimeParseException: Text '2024-04-19' could not be parsed at index 10
	at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2052)
	at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1954)
	at java.base/java.time.ZonedDateTime.parse(ZonedDateTime.java:600)
	at org.eclipse.yasson.internal.deserializer.types.DateDeserializer.parseWithOrWithoutZone(DateDeserializer.java:50)
	at org.eclipse.yasson.internal.deserializer.types.DateDeserializer.parseDefault(DateDeserializer.java:39)
	at org.eclipse.yasson.internal.deserializer.types.DateDeserializer.parseDefault(DateDeserializer.java:24)
	at org.eclipse.yasson.internal.deserializer.types.AbstractDateDeserializer.lambda$actualDeserializer$4(AbstractDateDeserializer.java:73)
	at org.eclipse.yasson.internal.deserializer.types.AbstractDateDeserializer.deserializeStringValue(AbstractDateDeserializer.java:90)
	at org.eclipse.yasson.internal.deserializer.types.TypeDeserializer.deserialize(TypeDeserializer.java:37)
	at org.eclipse.yasson.internal.deserializer.ValueExtractor.deserialize(ValueExtractor.java:47)
	at org.eclipse.yasson.internal.deserializer.ValueExtractor.deserialize(ValueExtractor.java:24)
	at org.eclipse.yasson.internal.deserializer.PositionChecker.deserialize(PositionChecker.java:85)
	at org.eclipse.yasson.internal.deserializer.PositionChecker.deserialize(PositionChecker.java:34)
	at org.eclipse.yasson.internal.deserializer.NullCheckDeserializer.deserialize(NullCheckDeserializer.java:46)
	at org.eclipse.yasson.internal.deserializer.NullCheckDeserializer.deserialize(NullCheckDeserializer.java:26)
	at org.eclipse.yasson.internal.DeserializationContextImpl.deserializeItem(DeserializationContextImpl.java:138)
	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:102)
	at org.glassfish.jersey.jsonb.internal.JsonBindingProvider.readFrom(JsonBindingProvider.java:91)
	at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.invokeReadFrom(ReaderInterceptorExecutor.java:233)
	at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:212)
	at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
	at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundReadFrom(MappableExceptionWrapperInterceptor.java:49)
	at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
	at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1072)
	at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:919)
	at org.glassfish.jersey.server.ContainerRequest.readEntity(ContainerRequest.java:290)
	at org.glassfish.jersey.server.internal.inject.EntityParamValueParamProvider$EntityValueSupplier.apply(EntityParamValueParamProvider.java:73)
	at org.glassfish.jersey.server.internal.inject.EntityParamValueParamProvider$EntityValueSupplier.apply(EntityParamValueParamProvider.java:56)
	at org.glassfish.jersey.server.spi.internal.ParamValueFactoryWithSource.apply(ParamValueFactoryWithSource.java:50)
	at org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:68)
	at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$AbstractMethodParamInvoker.getParamValues(JavaResourceMethodDispatcherProvider.java:109)
	at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:219)
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:93)
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:478)
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:400)
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
	at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:261)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
	at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:240)
	at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:697)
	at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:394)
	at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:357)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:311)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205)
	at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1368)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:120)
	at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:563)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:504)
	at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:71)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:121)
	at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:295)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:188)
	at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:425)
	at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:144)
	at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:174)
	at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:153)
	at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:196)
	at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:88)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:246)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:178)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:118)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:96)
	at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:51)
	at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:510)
	at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:82)
	at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:83)
	at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:101)
	at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:535)
	at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:515)
	at java.base/java.lang.Thread.run(Thread.java:842)
]]

Steps to reproduce

Register an endpoint like the above one, and make a POST to it.

@dmatej
Copy link
Contributor

dmatej commented Apr 21, 2024

I believe you cannot mix java.util.Date (rather obsoleted type) and Java Time api. Replace the java.util.Date time you used with the java.time.LocalDate.

@pzygielo
Copy link
Contributor

pzygielo commented Apr 21, 2024

Not exactly this:

But it perhaps could be discussed with yasson project.

@black-pwq
Copy link
Author

I believe you cannot mix java.util.Date (rather obsoleted type) and Java Time api. Replace the java.util.Date time you used with the java.time.LocalDate.

@dmatej Thanks for you suggestion. And I agree that java.util.Date is not the perfect object for time-related conversion (I use it mainly for compatibility reason). However, in my tests, format like "2020-12-12T17:17" will be deserialized to a Date object leaving the seconds field to be zero. I failed to find what are the accepted ISO 8601 formats in JSON-B spec. I am new to Jakarta EE and glassfish, if I miss something, please tell me. Thanks!

@OndroMih
Copy link
Contributor

Looking at Yasson code, it looks like Yasson only tries to parse Date+Time (https://github.com/eclipse-ee4j/yasson/blob/master/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java#L47) and does respect what the spec says in https://jakarta.ee/specifications/jsonb/3.0/jakarta-jsonb-spec-3.0#java-util-date-calendar-gregoriancalendar:

The serialization format of java.util.Date, Calendar, GregorianCalendar instances with no time information is ISO_DATE.

If time information is present, the format is ISO_DATE_TIME.

This should be fixed in Yasson. I'll see if I can fix it there.

@OndroMih
Copy link
Contributor

By the way, @black-pwq , until this is fixed, you can use a Jakarta REST provider to change the default date format. If you want to change the date format only for this specific REST method, it's also possible using reflection in the provider, like this:

@Provider
public class JSONConfigurator implements ContextResolver<Jsonb> {

    @Context
    private ResourceInfo ri;

    @Override
    public Jsonb getContext(Class<?> type) {

        // Configures JSON-B mapper to use 
        JsonbConfig config = new JsonbConfig();

        /* Uses injected context ResourceInfo to check whether
           the resource method to be executed is on the TestResource class and has @Path("test") annotation.
           Only for that method it will configure JSON Binding to use a ISO_DATE format
         */
        if (TestResource.class.isAssignableFrom(ri.getResourceClass())
                && ri.getResourceMethod().isAnnotationPresent(Path.class)) {
            String path = ri.getResourceMethod().getAnnotation(Path.class).value();
            if ("test".equals(path)) {
                  config.withDateFormat("yyyy-MM-dd[XXX]", null);
            }
        }

        return JsonbBuilder.create(config);
    }
}

@OndroMih
Copy link
Contributor

There's an issue for this already raised in Yasson project: eclipse-ee4j/yasson#487

@OndroMih
Copy link
Contributor

I raised a pull request in Yasson: eclipse-ee4j/yasson#648

@black-pwq , @pzygielo , please review it and let me know if you think UTC should be used instead of the local timezone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants