Avro 中的动态模式和嵌套地图

dymamic Schemas and nested Maps in Avro

我是 Avro 新手,正在尝试编写一些代码来序列化一些嵌套对象。

对象的结构如下所示:

class Parcel  {
    String recipe;
    Map<Integer, PluginDump> dumps;
}

class PluginDump  {
   byte[] state;
   Map<String, Param> params;
}


class Param {
   Type type;  //can be e.g. StringType, BooleanType, etc
   Object value;
}

所以我不能使用静态 avro 模式 - 每个 PluginDump 都会有不同的模式,具体取决于其中的类型。

我写了一些代码,可以根据单个 PluginDump 生成架构。

那么在序列化 Parcel 时,我如何 'put' 每个 PluginDump 条目?

这是我的代码:

Schema parcelSchema = AvroHelper.getSchema(p);
GenericRecord parcelRecord = new GenericData.Record(parcelSchema);
parcelRecord.put("recipe", p.getRecipe().toJson());
for (Map.Entry<Integer, PluginDump> entry : p.getDumps().entrySet()) {
        PluginDump dump = entry.getValue();
        Integer uid = entry.getKey();
        Schema dumpSchema = AvroHelper.getSchema(dump);//will be different for each PluginDump
        parcelRecord.put(????

有什么想法吗?

我感觉我的方法不对,但我在动态模式生成或嵌套映射的文档中找不到任何示例。

1 当你得到 GenericRecord parcelRecord = new GenericData.Record(parcelSchema); 你的记录中有两个字段:recipe 和 dumps,所以你不能遍历 dumps,你必须准备好在第二个记录字段中映射转储,就像您为菜谱所做的那样:parcelRecord.put("dumps", dumps);。但在这种情况下,您将得到 ClassCastException,因为 PluginDump 无法转换为 org.apache.avro.generic.IndexedRecord,因此您需要在 parcelRecord 中放入一个 GenericRecords 的 Map。 Map<String, Param> params 也需要这个,因为 Param 也不能转换为 IndexedRecord。

2 然后,我认为最好使用列表而不是地图,因为 avro 不能很好地处理具有不同类型的键和值的地图。

3 关于Param class:如果您将使用自动生成的模式,Param class 将像这样显示。

"type": "record",
"name": "Param",
"fields": [
    {
        "name": "type",
        "type": {
            "type": "record",
            "name": "Type",
            "namespace": "java.lang.reflect",
            "fields": []
        }
    },
    {
        "name": "value",
        "type": {
            "type": "record",
            "name": "Object",
            "namespace": "java.lang",
            "fields": []
        }
    }
]

就avro使用java.lang.reflect而言,你会在反序列化后丢失类型字段,avro将不知道它是什么类型。

如果你想为每个 Param 手动生成 avro-schema,考虑到它的类型,你可以这样做(我使用了来自 apache commons-lang3 的 ClassUtils.getClass,因为标准 Class.forName 方法并不总是正常工作):

public Schema getParamSchema() throws ClassNotFoundException {
        List<Schema.Field> fields = new ArrayList<>();

        fields.add(new Schema.Field("key", Schema.create(Schema.Type.STRING), "Doc: key field", (Object) null));
        Schema.Field f = new Schema.Field("type", ReflectData.get().getSchema(ClassUtils.getClass(((Class) this.type).getName())), "Doc: type field", (Object) null);
        f.addProp("java-class", ((Class) this.type).getName());
        fields.add(f);
        fields.add(new Schema.Field("value", ReflectData.get().getSchema(value.getClass()), "Doc: value field", (Object) null));

        return Schema.createRecord(((Class) this.type).getName() + "Param", "Doc: param record", this.getClass().getPackage().getName(), false, fields);
    }

但在这种情况下,avro 将抛出 ClassCastException,因为它无法将 Class 转换为布尔值、整数等。我在使用 avro 和 java 类型和 Classes.

所以我认为最好的建议是更改模型(我的意思是 Parcel、PluginDump 和 Param)以减少 avro 的问题。例如,您可以将类型名称存储为字符串,并在反序列化后获得带有反射的类型。