Java 从 POJO 到 Avro Record 的通用转换
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31207768/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic conversion from POJO to Avro Record
提问by Fabian Braun
I'm looking for a way to convert a POJO to an avro object in a generic way. The implementation should be robust to any changes of the POJO-class. I have achieved it but filling the avro record explicitly (see example below).
我正在寻找一种以通用方式将 POJO 转换为 avro 对象的方法。实现应该对 POJO 类的任何更改都是健壮的。我已经实现了,但明确填写了 avro 记录(请参见下面的示例)。
Is there a way to get rid of the hard-coded field names and just fill the avro record from the object? Is reflection the only way, or does avro provide this functionality out of the box?
有没有办法摆脱硬编码的字段名称并从对象中填充 avro 记录?反射是唯一的方法,还是 avro 提供开箱即用的功能?
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData.Record;
import org.apache.avro.reflect.ReflectData;
public class PojoToAvroExample {
static class PojoParent {
public final Map<String, String> aMap = new HashMap<String, String>();
public final Map<String, Integer> anotherMap = new HashMap<String, Integer>();
}
static class Pojo extends PojoParent {
public String uid;
public Date eventTime;
}
static Pojo createPojo() {
Pojo foo = new Pojo();
foo.uid = "123";
foo.eventTime = new Date();
foo.aMap.put("key", "val");
foo.anotherMap.put("key", 42);
return foo;
}
public static void main(String[] args) {
// extract the avro schema corresponding to Pojo class
Schema schema = ReflectData.get().getSchema(Pojo.class);
System.out.println("extracted avro schema: " + schema);
// create avro record corresponding to schema
Record avroRecord = new Record(schema);
System.out.println("corresponding empty avro record: " + avroRecord);
Pojo foo = createPojo();
// TODO: to be replaced by generic variant:
// something like avroRecord.importValuesFrom(foo);
avroRecord.put("uid", foo.uid);
avroRecord.put("eventTime", foo.eventTime);
avroRecord.put("aMap", foo.aMap);
avroRecord.put("anotherMap", foo.anotherMap);
System.out.println("expected avro record: " + avroRecord);
}
}
回答by timbaileyjones
I needed exactly such a thing myself. The library you need is in avro JAR files, but strangely, doesn't seem to have a way to invoke it from the avro-tools command line.
我自己也需要这样的东西。您需要的库位于 avro JAR 文件中,但奇怪的是,似乎没有办法从 avro-tools 命令行调用它。
Invoke it as: java GenerateSchemaFromPOJO com.example.pojo.Person Person.java
调用它:java GenerateSchemaFromPOJO com.example.pojo.Person Person.java
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import org.apache.avro.Schema;
import com.fasterxml.Hymanson.databind.ObjectMapper;
import com.fasterxml.Hymanson.dataformat.avro.AvroFactory;
import com.fasterxml.Hymanson.dataformat.avro.AvroSchema;
import com.fasterxml.Hymanson.dataformat.avro.schema.AvroSchemaGenerator;
import com.fasterxml.Hymanson.dataformat.avro.schema.VisitorFormatWrapperImpl;
public class GenerateSchemaFromPOJO {
public static void main(String[] args) {
String className = null;
String outputFile = null;
Writer outputWriter = null;
try {
if(args.length != 2) {
System.out.println("Usage: java " + GenerateSchemaFromPOJO.class.getCanonicalName() + " classname output-schema-file.json");
System.exit(1);
}
className = args[0];
outputFile = args[1];
Class<?> clazz = Class.forName(className);
AvroFactory avroFactory = new AvroFactory();
ObjectMapper mapper = new ObjectMapper(avroFactory);
AvroSchemaGenerator gen = new AvroSchemaGenerator();
mapper.acceptJsonFormatVisitor(clazz, gen);
AvroSchema schemaWrapper = gen.getGeneratedSchema();
Schema avroSchema = schemaWrapper.getAvroSchema();
String asJson = avroSchema.toString(true);
outputWriter = new FileWriter(outputFile);
outputWriter.write(asJson);
} catch (Exception ex) {
System.err.println("caught " + ex);
ex.printStackTrace();
System.exit(1);
} finally {
if(outputWriter != null) {
try {
outputWriter.close();
} catch (IOException e) {
System.err.println("Caught " + e + " while trying to close outputWriter to " + outputFile);;
e.printStackTrace();
}
}
}
}
}
回答by big
Here is generic way to convert
这是转换的通用方法
public static <V> byte[] toBytesGeneric(final V v, final Class<V> cls) {
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
final Schema schema = ReflectData.get().getSchema(cls);
final DatumWriter<V> writer = new ReflectDatumWriter<V>(schema);
final BinaryEncoder binEncoder = EncoderFactory.get().binaryEncoder(bout, null);
try {
writer.write(v, binEncoder);
binEncoder.flush();
} catch (final Exception e) {
throw new RuntimeException(e);
}
return bout.toByteArray();
}
public static void main(String[] args) {
PojoClass pojoObject = new PojoClass();
toBytesGeneric(pojoObject, PojoClass.class);
}
回答by TranceMaster
Are you using Spring?
你在用弹簧吗?
I build a mapper for that using a Spring feature. But it is also possible to build such a mapper via raw reflection utils too:
我使用 Spring 功能为此构建了一个映射器。但是也可以通过原始反射实用程序构建这样的映射器:
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.reflect.ReflectData;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.util.Assert;
public class GenericRecordMapper {
public static GenericData.Record mapObjectToRecord(Object object) {
Assert.notNull(object, "object must not be null");
final Schema schema = ReflectData.get().getSchema(object.getClass());
final GenericData.Record record = new GenericData.Record(schema);
schema.getFields().forEach(r -> record.put(r.name(), PropertyAccessorFactory.forDirectFieldAccess(object).getPropertyValue(r.name())));
return record;
}
public static <T> T mapRecordToObject(GenericData.Record record, T object) {
Assert.notNull(record, "record must not be null");
Assert.notNull(object, "object must not be null");
final Schema schema = ReflectData.get().getSchema(object.getClass());
Assert.isTrue(schema.getFields().equals(record.getSchema().getFields()), "Schema fields didn't match");
record.getSchema().getFields().forEach(d -> PropertyAccessorFactory.forDirectFieldAccess(object).setPropertyValue(d.name(), record.get(d.name()) == null ? record.get(d.name()) : record.get(d.name()).toString()));
return object;
}
}
With this mapper you can generate a GenericData.Record which can be easily serialized to avro. When you deserialize an Avro ByteArray you can use it to rebuild a POJO from deserialized record:
使用此映射器,您可以生成可轻松序列化为 avro 的 GenericData.Record。当您反序列化一个 Avro ByteArray 时,您可以使用它从反序列化的记录中重建一个 POJO:
Serialize
连载
byte[] serialized = avroSerializer.serialize("topic", GenericRecordMapper.mapObjectToRecord(yourPojo));
Deserialize
反序列化
GenericData.Record deserialized = (GenericData.Record) avroDeserializer.deserialize("topic", serialized);
YourPojo yourPojo = GenericRecordMapper.mapRecordToObject(deserialized, new YourPojo());
回答by Leon
With Hymanson/avro, it's very easy to convert pojo to byte[], similar to Hymanson/json:
使用Hymanson/avro,将 pojo 转换为 byte[] 非常容易,类似于 Hymanson/json:
byte[] avroData = avroMapper.writer(schema).writeValueAsBytes(pojo);
p.s.
Hymanson handles not only JSON, but also XML/Avro/Protobuf/YAML etc, with very similar classes and APIs.
ps
Hymanson 不仅处理 JSON,还处理 XML/Avro/Protobuf/YAML 等,具有非常相似的类和 API。
回答by longliveenduro
In addition to my comment to @TranceMaster answer the modified version below works for me with primitive types and Java sets:
除了我对@TranceMaster 回答的评论之外,下面的修改版本适用于原始类型和 Java 集:
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.reflect.ReflectData;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.util.Assert;
public class GenericRecordMapper {
public static GenericData.Record mapObjectToRecord(Object object) {
Assert.notNull(object, "object must not be null");
final Schema schema = ReflectData.get().getSchema(object.getClass());
System.out.println(schema);
final GenericData.Record record = new GenericData.Record(schema);
schema.getFields().forEach(r -> record.put(r.name(), PropertyAccessorFactory.forDirectFieldAccess(object).getPropertyValue(r.name())));
return record;
}
public static <T> T mapRecordToObject(GenericData.Record record, T object) {
Assert.notNull(record, "record must not be null");
Assert.notNull(object, "object must not be null");
final Schema schema = ReflectData.get().getSchema(object.getClass());
Assert.isTrue(schema.getFields().equals(record.getSchema().getFields()), "Schema fields didn't match");
record
.getSchema()
.getFields()
.forEach(field ->
PropertyAccessorFactory
.forDirectFieldAccess(object)
.setPropertyValue(field.name(), record.get(field.name()))
);
return object;
}
}