2015-07-03 3 views
9

Ich bin auf der Suche nach einer Möglichkeit, ein POJO in ein generisches avro-Objekt zu konvertieren. Die Implementierung sollte robust gegenüber allen Änderungen der POJO-Klasse sein. Ich habe es erreicht, aber den avro-Datensatz explizit gefüllt (siehe Beispiel unten).Generische Konvertierung von POJO zu Avro Record

Gibt es eine Möglichkeit, die fest codierten Feldnamen loszuwerden und nur den Avro-Datensatz aus dem Objekt zu füllen? Ist Reflektion der einzige Weg, oder bietet avro diese Funktionalität sofort an?

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); 
    } 
} 
+1

Warum nicht verwenden [Avro des ReflectDatumWriter] (http: // Stackoverflow .com/questions/11866466/using-apache-avro-reflect) um das POJO zu serialisieren? –

+0

Ich benutze Avro im Hadoop-Kontext. Für Serialisierung würde ich gerne das AvroParquetOutputFormat verwenden – fab

+1

Ein ineffizienter Ansatz hätte [ReflectDatumWriter schreiben ein POJO in Bytes dann GenericDatumReader liest die Bytes zu GenericRecord] (http://stackoverflow.com/questions/26435299/write-pojos-to-parquet -file-using-reflection). –

Antwort

-1

Ich brauchte genau so etwas selbst. Die Bibliothek, die Sie benötigen, befindet sich in avro JAR-Dateien, aber seltsamerweise scheint es keine Möglichkeit zu haben, sie über die Befehlszeile von avro-tools aufzurufen.

Invoke es als: 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.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.dataformat.avro.AvroFactory; 
import com.fasterxml.jackson.dataformat.avro.AvroSchema; 
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; 
import com.fasterxml.jackson.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(); 
       } 
      } 
     } 
    } 
} 
+0

Wie ich aus Ihrer Antwort verstehe, generiert Ihr Code das avro-Schema für den gegebenen "clazz". Das ist nicht das, wonach ich in der Frage gefragt habe. Ich mache dasselbe in der Zeile 'ReflectData.get(). GetSchema (Pojo.class);'. Ich suchte nach einer Möglichkeit, 'avroRecord.put (..., ...);' durch eine generische Variante zu ersetzen – fab

3

Hier ist allgemeiner Weise

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); 
} 
5

zu konvertieren Sind Sie Frühling mit? Ich baute einen Mapper dafür mit einer Spring-Funktion. Aber es ist auch möglich, dass Mapper zu bauen über rohe Reflexion utils zu:

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; 
    } 

} 

Mit diesem Mapper Sie eine GenericData.Record erzeugen kann, die leicht zu Avro serialisiert werden kann. Wenn Sie eine Avro deserialisieren bytearray können Sie es verwenden, um einen pojo von deserialisiert Datensatz neu zu erstellen:

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());