jackson-dataformats-binary icon indicating copy to clipboard operation
jackson-dataformats-binary copied to clipboard

Inability to specify custom namespace for `java-key-class` entries when using `@AvroNamespace` annotation in those classes

Open MartinK99 opened this issue 3 months ago • 2 comments

Hey, so when generating a avro schema using the AvroMapper and AvroSchemaGenerator, thanks to #310 we can specify the custom namespace each class should have. Now, given a class

ExampleClass.java
package com.example.test

import com.fasterxml.jackson.dataformat.avro.annotation.AvroNamespace;

import java.util.Map;

@AvroNamespace("com.github.test")
public class ExampleClass {

    public Map<ExampleEnum, String> exampleMap;
}

and the enum

ExampleEnum.java
package com.example.test

import com.fasterxml.jackson.dataformat.avro.annotation.AvroNamespace;

@AvroNamespace("com.github.test")
public enum ExampleEnum {
    FOO, BAR
}

, generating the schema by calling both classes explicitely (since nested generation doesn't work here) will yield

ExampleClass.avsc
{
  "type" : "record",
  "name" : "ExampleClass",
  "namespace" : "com.github.test",
  "fields" : [ {
    "name" : "exampleMap",
    "type" : [ "null", {
      "type" : "map",
      "values" : "string",
      "java-key-class" : "com.example.test"
    } ]
  } ]
}

and

ExampleEnum.avsc
{
  "type" : "enum",
  "name" : "ExampleEnum",
  "namespace" : "com.github.test",
  "symbols" : [ "FOO", "BAR" ]
}

Notice how the specified java-key-class has the wrong namespace defined, since it is simply resolving the package name of the existing Java object. The likely correct way would be to fetch the contents of the @AvroNamespace annotation inside the enum class and resolve it that way when a java-key-class entry is generated, if possible, and otherwise resolve it the old way. Because I'm not sure of that possiblity though, another suggestion would be to implement an @AvroKeyClass annotation to be able to manually override that package definition.

MartinK99 avatar Sep 03 '25 16:09 MartinK99

Unfortunately it only seems to be part of the issue, because even if those classes are referenced correctly, there's a problem during the generation of the avro classes (using avro-tools cmd in my case), where it tries to create a new object for every key (which doesn't work with enums). Relevant generated example of a HashMap<Enum1, Enum2>:

ExampleClass.java#customDecode(org.apache.avro.io.ResolvingDecoder in) (generated)

[...]
for ( ; 0 < size0; size0 = in.mapNext()) {
  for ( ; size0 != 0; size0--) {
    de.vwag.cyclone.avro.model.heating.EventType k0 = null;
    k0 = new de.vwag.cyclone.avro.model.heating.EventType(in.readString());
    de.vwag.cyclone.avro.model.heating.EventCondition v0 = null;
    v0 = de.vwag.cyclone.avro.model.heating.EventCondition.values()[in.readEnum()];
    m0.put(k0, v0);
  }
}
[...]

For now I resorted to treating the keys as strings by removing the java-key-class entries by hand.

MartinK99 avatar Sep 04 '25 10:09 MartinK99

Thank you for reporting this @MartinK99.

At this point we'd probably need a contribution to fix it.

cowtowncoder avatar Sep 04 '25 21:09 cowtowncoder