Avroスキーマを使用してJSONファイルを検証し、対応するAvroファイルを書き込もうとしています。まず、user.avsc
という名前の次のAvroスキーマを定義しました。
{"namespace": "example.avro",
"type": "record",
"name": "user",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["int", "null"]},
{"name": "favorite_color", "type": ["string", "null"]}
]
}
次に、user.json
ファイルを作成しました:
{"name": "Alyssa", "favorite_number": 256, "favorite_color": null}
そして、実行しようとしました:
Java -jar ~/bin/avro-tools-1.7.7.jar fromjson --schema-file user.avsc user.json > user.avro
しかし、次の例外が発生します。
Exception in thread "main" org.Apache.avro.AvroTypeException: Expected start-union. Got VALUE_NUMBER_INT
at org.Apache.avro.io.JsonDecoder.error(JsonDecoder.Java:697)
at org.Apache.avro.io.JsonDecoder.readIndex(JsonDecoder.Java:441)
at org.Apache.avro.io.ResolvingDecoder.doAction(ResolvingDecoder.Java:290)
at org.Apache.avro.io.parsing.Parser.advance(Parser.Java:88)
at org.Apache.avro.io.ResolvingDecoder.readIndex(ResolvingDecoder.Java:267)
at org.Apache.avro.generic.GenericDatumReader.read(GenericDatumReader.Java:155)
at org.Apache.avro.generic.GenericDatumReader.readField(GenericDatumReader.Java:193)
at org.Apache.avro.generic.GenericDatumReader.readRecord(GenericDatumReader.Java:183)
at org.Apache.avro.generic.GenericDatumReader.read(GenericDatumReader.Java:151)
at org.Apache.avro.generic.GenericDatumReader.read(GenericDatumReader.Java:142)
at org.Apache.avro.tool.DataFileWriteTool.run(DataFileWriteTool.Java:99)
at org.Apache.avro.tool.Main.run(Main.Java:84)
at org.Apache.avro.tool.Main.main(Main.Java:73)
何か不足していますか? 「予期した開始ユニオン。VALUE_NUMBER_INTを取得しました」と表示されるのはなぜですか。
ダグ・カッティングによる説明 によれば、
AvroのJSONエンコーディングでは、null以外のunion値に目的のタイプのタグを付ける必要があります。これは、["bytes"、 "string"]や["int"、 "long"]などのユニオンがJSONではあいまいであるため、最初のユニオンは両方ともJSON文字列としてエンコードされ、2番目のユニオンは両方ともJSON番号としてエンコードされます。
http://avro.Apache.org/docs/current/spec.html#json_encoding
したがって、レコードは次のようにエンコードする必要があります。
{"name": "Alyssa", "favorite_number": {"int": 7}, "favorite_color": null}
この一般的な問題に対処する必要がある作業に新しいJSONエンコーダーがあります。
ユニオンとその検証を実装しました。ユニオンスキーマを作成し、その値をpostmanに渡します。 resgistry urlは、kafkaのプロパティに指定するURLです。uは動的な値をスキーマに渡すこともできます。
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<String> response = template.exchange(""+registryUrl+"/subjects/"+topic+"/versions/"+version+"", HttpMethod.GET, entity, String.class);
String responseData = response.getBody();
JSONObject jsonObject = new JSONObject(responseData);
JSONObject jsonObjectResult = new JSONObject(jsonResult);
String getData = jsonObject.get("schema").toString();
Schema.Parser parser = new Schema.Parser();
Schema schema = parser.parse(getData);
GenericRecord genericRecord = new GenericData.Record(schema);
schema.getFields().stream().forEach(field->{
genericRecord.put(field.name(),jsonObjectResult.get(field.name()));
});
GenericDatumReader<GenericRecord>reader = new GenericDatumReader<GenericRecord>(schema);
boolean data = reader.getData().validate(schema,genericRecord );
@ Emre-Sevincが指摘したように、問題はAvroレコードのエンコードにあります。
ここでより具体的に説明します。
これを行わないでください:
jsonRecord = avroGenericRecord.toString
代わりに、次のようにします。
val writer = new GenericDatumWriter[GenericRecord](avroSchema)
val baos = new ByteArrayOutputStream
val jsonEncoder = EncoderFactory.get.jsonEncoder(avroSchema, baos)
writer.write(avroGenericRecord, jsonEncoder)
jsonEncoder.flush
val jsonRecord = baos.toString("UTF-8")
次のインポートも必要です。
import org.Apache.avro.Schema
import org.Apache.avro.generic.{GenericData, GenericDatumReader, GenericDatumWriter, GenericRecord}
import org.Apache.avro.io.{DecoderFactory, EncoderFactory}
これを実行すると、目的の型でタグ付けされたnull以外のunion値を含むjsonRecord
が得られます。
お役に立てれば !