將 JSON 轉換為 Avro 對象
一、簡介
在本教程中,我們將探討如何將 JSON 資料轉換為 Java 中的 Apache Avro 物件。 Avro 是一個資料序列化框架,以緊湊的格式提供豐富的資料結構和二進位資料。此外,與其他序列化框架不同,Avro 使用以 JSON 格式定義的模式,而不需要產生程式碼來進行序列化。
因此,它的關鍵優勢之一是支持模式演化。這樣,Avro 特別適合需要處理隨時間變化的資料結構的應用程式。此外,由於其緊湊的資料格式,它對於處理大量資料的應用程式非常有用。
2. JSON 到 Avro 的轉換
在 Avro 中,從 JSON 轉換為物件需要一個建立資料結構和轉換機制的模式。在我們的例子中,這種轉換機制將在convertJsonToAvro()
方法中。
模式定義資料的格式(包括欄位名稱和類型),而方法使用此模式將 JSON 轉換為 Avro 物件。
2.1.實作轉換方法
首先,讓我們為 pom.xml 加入必要的依賴項pom.xml:
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.12.0</version>
</dependency>
接下來,讓我們建立一個模式來定義 JSON 應遵循的結構:
private static final String SCHEMA_JSON = """
{
"type": "record",
"name": "Customer",
"namespace": "com.baeldung.avro",
"fields": [
{"name": "name", "type": "string"},
{"name": "age", "type": "int"},
{"name": "email", "type": ["null", "string"], "default": null}
]
}""";
接下來,我們建立一個轉換器方法來處理 JSON 到 Avro 的轉換。此外,轉換過程涉及三個主要元件:模式、根據模式讀取 JSON 資料的解碼器以及建立 Avro 物件的DatumReader
。
讓我們建立該方法:
GenericRecord convertJsonToAvro(String json) throws IOException {
try {
DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);
Decoder decoder = DecoderFactory.get().jsonDecoder(schema, json);
return reader.read(null, decoder);
} catch (IOException e) {
throw new IOException("Error converting JSON to Avro", e);
}
}
使用我們實例化的解碼器,我們讀取 JSON 輸入並確保它與我們的模式結構相符。最後,透過DatumReader
我們使用架構和解碼器來建立GenericRecord
物件。這樣,Avro 就無需產生類別即可表示資料。
在將 JSON 轉換為 Avro 時,我們通常遵循以下步驟:
- JSON 輸入根據架構進行驗證
- 解碼器根據 schema 的結構解析 JSON
-
DatumReader
建立一個包含資料的GenericRecord
- JSON 中不存在但在架構中定義的任何欄位都會指派其預設值
需要注意的一個重要方面是 Avro 如何處理聯合類型。當我們將欄位定義為聯合時(例如: [“null”, “string”]
),JSON 表示形式必須明確指定所使用的類型。例如,我們必須將字串值包裝在 JSON 物件中,類型為鍵: {“string”: “value”}.
因此,這與我們直接使用值的常規 JSON 不同。
2.2.測試 JSON 到 Avro 的轉換
現在,讓我們測試一下我們的實作:
@Test
void whenValidJsonInput_thenConvertsToAvro() throws IOException {
JsonToAvroConverter converter = new JsonToAvroConverter();
String json = "{\"name\":\"John Doe\",\"age\":30,\"email\":{\"string\":\"[email protected]\"}}";
GenericRecord record = converter.convertJsonToAvro(json);
assertEquals("John Doe", record.get("name").toString());
assertEquals(30, record.get("age"));
assertEquals("[email protected]", record.get("email").toString());
}
此測試驗證我們的轉換器是否正確處理完整的 JSON 物件(每個欄位都已填入)。此外,讓我們注意email
欄位的特殊格式。這使用 Avro 的聯合類型語法。
從測試中我們可以看到,所有類型都已正確轉換並可在GenericRecord
result
變數中存取。
讓我們來看看下一個測試,我們將帶有null
欄位的 JSON 轉換為GenericRecord
:
@Test
void whenJsonWithNullableField_thenConvertsToAvro() throws IOException {
JsonToAvroConverter converter = new JsonToAvroConverter();
String json = "{\"name\":\"John Doe\",\"age\":30,\"email\":null}";
GenericRecord record = converter.convertJsonToAvro(json);
assertEquals("John Doe", record.get("name").toString());
assertEquals(30, record.get("age"));
assertNull(record.get("email"));
}
此測試確認我們正在正確轉換可選email
欄位中的null
值。我們在架構中將email
欄位定義為[“null”, “string”]
的並集。因此,它接受null
值。
3. 高級使用
透過從 JSON 到 Avro 物件的基本轉換,我們涵蓋了許多常見情況。然而,現實世界的應用程式通常需要更複雜的操作。
讓我們來看看兩個場景:處理 JSON 陣列(對於資料集處理至關重要)和二進位序列化。後者準備資料以供儲存或通訊。
3.1.處理 JSON 數組
有時,我們需要一次處理多個 JSON 物件。考慮到這一點,讓我們擴展我們的轉換器來處理 JSON 陣列。為此,我們創建一個新方法:
List<GenericRecord> convertJsonArrayToAvro(String jsonArray) throws IOException {
List<GenericRecord> records = new ArrayList<>();
Schema arraySchema = Schema.createArray(schema);
Decoder decoder = DecoderFactory.get().jsonDecoder(arraySchema, jsonArray);
DatumReader<List<GenericRecord>> reader = new GenericDatumReader<>(arraySchema);
List<GenericRecord> result = reader.read(null, decoder);
return result;
}
現在,讓我們分析一下我們的方法。首先,我們為現有記錄模式陣列建立一個模式。接下來,我們使用 Avro 的內建 JSON 解碼器來驗證我們的 JSON(“ arraySchema
” )
是否遵循架構中定義的結構,將每個字段轉換為其等效架構,然後相應地處理特殊情況,例如聯合類型。
最後,我們使用DatumReader
一次讀取整個陣列。
3.2.測試 JSON 數組的處理
現在,讓我們建立一個測試來驗證此方法:
@Test
void whenJsonArray_thenConvertsToAvroList() throws IOException {
JsonToAvroConverter converter = new JsonToAvroConverter();
String jsonArray = """
[
{"name":"John Doe","age":30,"email":{"string":"[email protected]"}},
{"name":"Jane Doe","age":28,"email":{"string":"[email protected]"}}
]""";
List<GenericRecord> records = converter.convertJsonArrayToAvro(jsonArray);
assertEquals(2, records.size());
assertEquals("John Doe", records.get(0).get("name").toString());
assertEquals("[email protected]", records.get(1).get("email").toString());
}
我們簡單分析一下測試。值得一提的是,對於定義為聯合的欄位(例如我們的email
欄位),我們需要維護正確的 Avro JSON 格式,即使在陣列中也是如此。
3.3.二進位序列化
雖然 JSON 到 Avro 物件對於資料處理很有用,但大多數應用程式需要以有效的格式儲存或傳輸此資料。因此, Avro 的二進位序列化比 JSON 或 XML 具有顯著的優勢。
其中一些優點是更緊湊的格式、更好的序列化/反序列化性能以及對模式演變的內建支援。 現在,讓我們寫一個方法來幫助我們解決這個問題。我們的serializeAvroRecord()
方法示範如何將GenericRecord
轉換為其二進位等價物,準備儲存或傳輸:
byte[] serializeAvroRecord(GenericRecord record) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DatumWriter<GenericRecord> writer = new GenericDatumWriter<>(schema);
BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(outputStream, null);
writer.write(record, encoder);
encoder.flush();
return outputStream.toByteArray();
}
3.4.測試二進位序列化
現在,讓我們建立一個測試來驗證此方法:
@Test
void whenSerializingAvroRecord_thenProducesByteArray() throws IOException {
String json = """
{"name":"John Doe","age":30,"email":{"string":"[email protected]"}}
""" ;
JsonToAvroConverter converter = new JsonToAvroConverter();
GenericRecord record = converter.convertJsonToAvro(json);
byte[] bytes = converter.serializeAvroRecord(record);
assertNotNull(bytes);
assertTrue(bytes.length > 0);
}
我們簡單分析一下測試。首先,我們將 JSON 轉換為GenericRecord
。接下來,我們使用我們的方法serializeAvroRecord()
以二進位格式序列化該記錄。最後,我們測試我們的方法是否產生一個非空且非空的位元組數組。
4. 結論
在本文中,我們探討如何在 Java 中將 JSON 資料轉換為 Avro 物件。我們已經討論了基本的轉換、處理陣列、序列化和驗證。
我們也討論了使用 Avro 的二進位序列化功能有利於其他選項的重要性。我們的解決方案為在 Java 應用程式中使用 JSON 和 Avro 提供了堅實的基礎。
與往常一樣,程式碼可以在 GitHub 上取得。