FasterXML / jackson-dataformats-binary

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

Add `AvroGenerator.Feature.ADD_NULL_AS_DEFAULT_VALUE_IN_SCHEMA` for adding default `null` in generated schema #145

Closed kucera-jan-cz closed 5 years ago

kucera-jan-cz commented 6 years ago

Hello, currently we are using for generation schema from POJO ReflectData.AllowNull.get() however we would like to switch to jackson-dataformats-binary. The only issue with the switch is absence of "default" value in schema (which we currently have in our schema registry and would break our compatibility). Is it possible to set this behavior through AvroFactory or AvroSchemaGenerator?

For clarification here is example from jackson-dataformats-binary: {"type":"record","name":"PersonV1","namespace":"dummy","fields":[{"name":"age","type":["null","string"]}]} And here example from ReflectData.AllowNull.get(): {"type":"record","name":"PersonV1","namespace":"dummy","fields":[{"name":"age","type":["null","string"],"default":null}]}

Thanks in advance for your answer

cowtowncoder commented 6 years ago

@kucera-jan-cz Could you include bit more information on code you are using: I assume you have a simple POJO definition (class PersonV1), and then create AvroSchema from it. But how are you serializing it to get output?

kucera-jan-cz commented 6 years ago

@cowtowncoder Hello and thanks for your reply. Yes, I am having simple POJO and serializing it with our own serializer with following code:

           int registeredSchemaId = this.schemaRegistry.register(subject, schema);

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            out.write(0);
            out.write(ByteBuffer.allocate(4).putInt(registeredSchemaId).array());

            DatumWriter<Object> dw = new ReflectDatumWriter<>(schema);
            Encoder encoder = ENCODER_FACTORY.directBinaryEncoder(out, null);
            dw.write(value, encoder);
            encoder.flush();
            return out.toByteArray();
baharclerode commented 6 years ago

Have you tried using @AvroDefault("null") or @JsonProperty(defaultValue = "null") on the age field?

cowtowncoder commented 6 years ago

@kucera-jan-cz I meant Jackson code used to produce output that is missing "default" entry. I am not sure I fully understand exact steps of desired Jackson dataformat solution.

baharclerode commented 6 years ago

Oh, I see what you're asking. Currently, Jackson will generate unions for optional fields:

package dummy;

public class PersonV1 {

    public PersonV1(String age) {
        this.age = age;
    }

    private String age;

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

Plus

AvroMapper mapper = new AvroMapper();
System.out.println(mapper.schemaFor(mapper.constructType(PersonV1.class)).getSchema()

gives

{
  "type" : "record",
  "name" : "PersonV1",
  "namespace" : "dummy",
  "fields" : [ {
    "name" : "age",
    "type" : [ "null", "string" ]
  } ]
}

But it doesn't generate defaults unless explicitly given one with @AvroDefault("null") or @JsonProperty(defaultValue = "null") because while it already allows nulls by default, it doesn't assume null is the default. We could probably make it add a default without much issue.

kucera-jan-cz commented 6 years ago

Thanks for your response @cowtowncoder and @baharclerode. Yes it's exactly as you mentioned in your last post - the only missing part is to enable adding "default":null in the schema. Since you mention that you can probably make it I have tried to implement in and send you a pull request.

Regards Jan

cowtowncoder commented 5 years ago

PR merged, so this is now in for 3.0.0 (and if someone has time & itch, possibly backport in 2.x, currently 2.10).