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

[avro] Unable to encode and decodea POJO with an auto-generated schema #309

Open
lburgazzoli opened this issue Jan 6, 2022 · 4 comments
Labels

Comments

@lburgazzoli
Copy link

I wrote a small example that is about encoding and decoding a POJO with an auto generated schema, the code is like:

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.slf4j:slf4j-simple:1.7.30
//DEPS com.fasterxml.jackson.core:jackson-core:2.12.5
//DEPS com.fasterxml.jackson.core:jackson-databind:2.12.5
//DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-avro:2.12.5
//DEPS com.slack.api:slack-api-model:1.8.1

import java.util.List;
import java.util.UUID;

import com.fasterxml.jackson.dataformat.avro.AvroMapper;
import com.slack.api.model.Message;
import com.slack.api.model.block.SectionBlock;
import com.slack.api.model.block.composition.MarkdownTextObject;


public class av {
    public static void main(String[] args) throws Exception {
        var message = new Message();
        message.setBlocks(List.of(SectionBlock
                .builder()
                .text(MarkdownTextObject
                        .builder()
                        .text("*Hello from Jackson!*")
                        .build())
                .build()));

        serdes(Message.class, message);
    }

    public static <T> void serdes(Class<T> type, T instance) throws Exception {
        var mapper = new AvroMapper();
        var schema = mapper.schemaFor(type);

        var raw = mapper.writer().with(schema).writeValueAsBytes(instance);
        var obj = mapper.readerFor(type).with(schema).readValue(raw);

        System.out.println("> " + raw);
        System.out.println("> " + obj);
    }
}

When running the code, then an exception is thrown:

xception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No field named 'accessory' (through reference chain: com.slack.api.model.Message["blocks"]->java.util.ImmutableCollections$List12[0]->com.slack.api.model.block.SectionBlock["accessory"])
        at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:390)
        at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:349)
        at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:778)
        at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
        at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
        at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
        at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1514)
        at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1215)
        at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsBytes(ObjectWriter.java:1108)
        at av.serdes(av.java:35)
        at av.main(av.java:28)
        Suppressed: com.fasterxml.jackson.core.JsonGenerationException: Failed to close AvroGenerator: (java.lang.NullPointerException): null of boolean in field displayAsBot of com.slack.api.model.Message
                at com.fasterxml.jackson.dataformat.avro.AvroGenerator.close(AvroGenerator.java:353)
                at com.fasterxml.jackson.databind.util.ClassUtil.closeOnFailAndThrowAsIOE(ClassUtil.java:497)
                at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1217)
                ... 3 more
        Caused by: java.lang.NullPointerException: null of boolean in field displayAsBot of com.slack.api.model.Message
                at org.apache.avro.generic.GenericDatumWriter.npe(GenericDatumWriter.java:145)
                at org.apache.avro.generic.GenericDatumWriter.writeWithoutConversion(GenericDatumWriter.java:139)
                at com.fasterxml.jackson.dataformat.avro.ser.NonBSGenericDatumWriter.write(NonBSGenericDatumWriter.java:123)
                at org.apache.avro.generic.GenericDatumWriter.write(GenericDatumWriter.java:62)
                at com.fasterxml.jackson.dataformat.avro.ser.RootContext.complete(RootContext.java:122)
                at com.fasterxml.jackson.dataformat.avro.AvroGenerator._complete(AvroGenerator.java:627)
                at com.fasterxml.jackson.dataformat.avro.AvroGenerator.close(AvroGenerator.java:348)
                ... 5 more
        Caused by: java.lang.NullPointerException
                at org.apache.avro.generic.GenericDatumWriter.writeWithoutConversion(GenericDatumWriter.java:134)
                at com.fasterxml.jackson.dataformat.avro.ser.NonBSGenericDatumWriter.write(NonBSGenericDatumWriter.java:43)
                at org.apache.avro.generic.GenericDatumWriter.writeField(GenericDatumWriter.java:166)
                at org.apache.avro.generic.GenericDatumWriter.writeRecord(GenericDatumWriter.java:156)
                at org.apache.avro.generic.GenericDatumWriter.writeWithoutConversion(GenericDatumWriter.java:118)
                ... 10 more
Caused by: java.lang.IllegalStateException: No field named 'accessory'
        at com.fasterxml.jackson.dataformat.avro.ser.ObjectWriteContext._reportUnknownField(ObjectWriteContext.java:136)
        at com.fasterxml.jackson.dataformat.avro.ser.ObjectWriteContext.writeFieldName(ObjectWriteContext.java:65)
        at com.fasterxml.jackson.dataformat.avro.AvroGenerator.writeFieldName(AvroGenerator.java:307)
        at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:694)
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
        ... 14 more

I'm not sure if this comes from how the Message class is implemented (unfortunately it is outside of my control) or about eh way I'm using jackson.

Any help would be appreciated.

@cowtowncoder
Copy link
Member

Looks like it should work, but to know for sure you would need to include definition of type Message.
It would probably be good to first debug with JSON writer/reader, see if there is an issue with POJO definition itself wrt round-trip handling.

@lburgazzoli
Copy link
Author

The message definition is here: https://github.com/slackapi/java-slack-sdk/blob/main/slack-api-model/src/main/java/com/slack/api/model/Message.java

I bet the issue is with the gson annotation that are being used

@lburgazzoli
Copy link
Author

Doing the same thing with plain json:

    public static <T> void json(Class<T> type, T instance) throws Exception {
        var mapper = new ObjectMapper();

        var raw = mapper.writer().writeValueAsBytes(instance);
        var obj = mapper.readerFor(type).readValue(raw);

        System.out.println("> " + raw);
        System.out.println("> " + obj);
    }

Leads to:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.slack.api.model.block.LayoutBlock` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (byte[])"{"type":null,"subtype":null,"team":null,"channel":null,"user":null,"username":null,"text":null,"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"*Hello from Jackson!*","verbatim":null},"blockId":null,"fields":null,"accessory":null}],"attachments":null,"ts":null,"threadTs":null,"intro":false,"starred":false,"wibblr":false,"pinnedTo":null,"reactions":null,"botId":null,"botLink":null,"displayAsBot":false,"botProfile":null,"icons":null,"file":null,"files":null,"upload":false,"parentUserId""[truncated 373 bytes]; line: 1, column: 106] (through reference chain: com.slack.api.model.Message["blocks"]->java.util.ArrayList[0])
        at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
        at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764)
        at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
        at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1209)
        at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
        at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355)
        at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
        at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
        at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
        at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2033)
        at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1528)
        at av.json(av.java:37)
        at av.main(av.java:29)

@cowtowncoder
Copy link
Member

Exception claims that LayoutBlock is abstract type: if so, there has to be something to tell which actual concrete type is to be constructed. This can be done by:

  1. Adding explicit mapping (either on mapper or using annotations like @JsonDeserialize(as = ConcreteType.class))
  2. Enabling polymorphic type handling (this requires addition of Type Id during serialization)

Gson annotations would not be used but you would likely need their equivalent for Jackson if default names are changed (which seems to be the case).

@cowtowncoder cowtowncoder changed the title [avro] Unable to encoding and decoding a POJO with an auto generated schema [avro] Unable to encode and decodea POJO with an auto-generated schema May 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants