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 的问题。例如,您可以将类型名称存储为字符串,并在反序列化后获得带有反射的类型。
我是 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 的问题。例如,您可以将类型名称存储为字符串,并在反序列化后获得带有反射的类型。