FasterXML / jackson-dataformats-binary

Uber-project for standard Jackson binary format backends: avro, cbor, ion, protobuf, smile
Apache License 2.0
316 stars 136 forks source link

Protobuf `LocalDateTime` serialization as Root value throws `UnsupportedOperationException` #483

Closed jj314 closed 2 months ago

jj314 commented 9 months ago

Hello, I'm having trouble serializing LocalDateTime with Protobuf.

Here is what I'm trying to do:

import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.dataformat.protobuf.ProtobufMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Data;

import java.time.LocalDateTime;

public class Main {
    @Data
    static class Foo {
        LocalDateTime bar;
    }

    public static void main(String[] args) throws JsonMappingException {
        var mapper = ProtobufMapper.builder()
                .addModule(new JavaTimeModule())
                .build();
        mapper.generateSchemaFor(Foo.class);
    }
}

but I get the following exception:

Exception in thread "main" java.lang.UnsupportedOperationException: 'Map' type not supported as type by protobuf module
    at com.fasterxml.jackson.dataformat.protobuf.schemagen.ProtoBufSchemaVisitor._throwUnsupported(ProtoBufSchemaVisitor.java:208)
    at com.fasterxml.jackson.dataformat.protobuf.schemagen.ProtoBufSchemaVisitor.expectArrayFormat(ProtoBufSchemaVisitor.java:109)
    at com.fasterxml.jackson.datatype.jsr310.ser.JSR310FormattedSerializerBase._acceptTimestampVisitor(JSR310FormattedSerializerBase.java:188)
    at com.fasterxml.jackson.datatype.jsr310.ser.JSR310FormattedSerializerBase.acceptJsonFormatVisitor(JSR310FormattedSerializerBase.java:175)
    at com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer.acceptJsonFormatVisitor(LocalDateTimeSerializer.java:37)
    at com.fasterxml.jackson.dataformat.protobuf.schemagen.MessageElementVisitor.acceptTypeElement(MessageElementVisitor.java:137)
    at com.fasterxml.jackson.dataformat.protobuf.schemagen.MessageElementVisitor.getDataType(MessageElementVisitor.java:121)
    at com.fasterxml.jackson.dataformat.protobuf.schemagen.MessageElementVisitor.buildFieldElement(MessageElementVisitor.java:86)
    at com.fasterxml.jackson.dataformat.protobuf.schemagen.MessageElementVisitor.optionalProperty(MessageElementVisitor.java:59)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.depositSchemaProperty(BeanPropertyWriter.java:843)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.acceptJsonFormatVisitor(BeanSerializerBase.java:912)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.acceptJsonFormatVisitor(DefaultSerializerProvider.java:588)
    at com.fasterxml.jackson.databind.ObjectMapper.acceptJsonFormatVisitor(ObjectMapper.java:4744)
    at com.fasterxml.jackson.databind.ObjectMapper.acceptJsonFormatVisitor(ObjectMapper.java:4723)
    at com.fasterxml.jackson.dataformat.protobuf.ProtobufMapper.generateSchemaFor(ProtobufMapper.java:144)
    at org.example.Main.main(Main.java:17)

Process finished with exit code 1

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>debug</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jackson.version>2.16.1</jackson.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-protobuf</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>
</project>
cowtowncoder commented 9 months ago

Sounds like serializer for LocalDateTime is not producing expected schema structure for given settings.

cowtowncoder commented 2 months ago

Quick note: the issue is due to Schema generation for Array types not working (NOT "Map" like exception claims -- I'll do PR for that minor flaw). This because by default LocalDateTime is serialize as Array of 3 integers. Until fix for schema generation to support arrays is added, the best way to work around this is to serialize LocalDateTime as String, not array: this can be done f.ex by disabling

SerializationFeature.WRITE_DATES_AS_TIMESTAMPS

setting on ProtobufMapper.

cowtowncoder commented 2 months ago

So: for anyone looking at this issue -- proper fix is to add Protobuf schema generation for Array of JsonFormatTypes.INTEGER as indicated by JSR310FormattedSerializerBase._acceptTimestampVisitor() of Date/Time format module.

But test should work by generating schema for, say, int[].

cowtowncoder commented 2 months ago

Ahhhhh. Not quite even that -- using a wrapper, like:

class LocalDateTimeWrapper {
   public LocalDateTime value;
}

would make things work. So it's only for root value that there is an issue.

cowtowncoder commented 2 months ago

Ok, now I remember: at root level, Protobuf cannot support Array values -- so as long as type like LocalDateTime is serialized as array, it cannot be serialized as the root value: it must be a property of a root-level object / record. This also applies if attempting to serialize as a String, fwtw, fundamental restriction.