diff --git a/docs/content.zh/docs/connectors/table/formats/raw.md b/docs/content.zh/docs/connectors/table/formats/raw.md
index 69b4acfa1a0f49..cb1f2aea4f6bb3 100644
--- a/docs/content.zh/docs/connectors/table/formats/raw.md
+++ b/docs/content.zh/docs/connectors/table/formats/raw.md
@@ -178,6 +178,17 @@ Format 参数
RAW
通过 RAW 类型的底层 TypeSerializer 序列化的字节序列。
+
+
VARIANT
+
A UTF-8 (by default) encoded JSON document.
+ The encoding charset can be configured by 'raw.charset'.
+ On read, the decoded text is parsed like PARSE_JSON, so duplicate object keys are rejected and
+ malformed JSON fails the job. On write, the value is rendered by Variant#toJson. The round trip is
+ value-lossless but not byte-lossless: insignificant whitespace is dropped and object keys are ordered.
+
+Note: combining `VARIANT` with `raw.line-delimiter` gives you newline-delimited JSON, where each line of a message
+becomes one row.
+
diff --git a/docs/content/docs/connectors/table/formats/raw.md b/docs/content/docs/connectors/table/formats/raw.md
index 1efdd2b40ee4a4..3e2a2c60e8bde0 100644
--- a/docs/content/docs/connectors/table/formats/raw.md
+++ b/docs/content/docs/connectors/table/formats/raw.md
@@ -180,6 +180,17 @@ The table below details the SQL types the format supports, including details of
RAW
The sequence of bytes serialized by the underlying TypeSerializer of the RAW type.
+
+
VARIANT
+
A UTF-8 (by default) encoded JSON document.
+ The encoding charset can be configured by 'raw.charset'.
+ On read, the decoded text is parsed like PARSE_JSON, so duplicate object keys are rejected and
+ malformed JSON fails the job. On write, the value is rendered by Variant#toJson. The round trip is
+ value-lossless but not byte-lossless: insignificant whitespace is dropped and object keys are ordered.
+
+Note: combining `VARIANT` with `raw.line-delimiter` gives you newline-delimited JSON, where each line of a message
+becomes one row.
+
diff --git a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatDeserializationSchema.java b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatDeserializationSchema.java
index 4ac4f82f464475..10558ebad1ffc9 100644
--- a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatDeserializationSchema.java
+++ b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatDeserializationSchema.java
@@ -29,6 +29,8 @@
import org.apache.flink.table.data.StringData;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.types.DeserializationException;
+import org.apache.flink.types.variant.BinaryVariantInternalBuilder;
+import org.apache.flink.types.variant.Variant;
import org.apache.flink.util.Collector;
import javax.annotation.Nullable;
@@ -210,6 +212,9 @@ private static DeserializationRuntimeConverter createConverter(
case RAW:
return RawValueData::fromBytes;
+ case VARIANT:
+ return createVariantConverter(charsetName);
+
case BOOLEAN:
return data -> data[0] != 0;
@@ -278,6 +283,39 @@ public Object convert(byte[] data) {
};
}
+ /**
+ * Creates a converter that decodes the bytes with the configured charset and parses the text as
+ * a JSON document into a {@link Variant}. Duplicate object keys are rejected, matching the
+ * default of {@code PARSE_JSON}.
+ */
+ private static DeserializationRuntimeConverter createVariantConverter(
+ final String charsetName) {
+ // this also checks the charsetName is valid
+ Charset.forName(charsetName);
+
+ return new DeserializationRuntimeConverter() {
+ private static final long serialVersionUID = 1L;
+ private transient Charset charset;
+
+ @Override
+ public void open() {
+ charset = Charset.forName(charsetName);
+ }
+
+ @Override
+ public Object convert(byte[] data) {
+ try {
+ return BinaryVariantInternalBuilder.parseJson(new String(data, charset), false);
+ } catch (Exception e) {
+ throw new DeserializationException(
+ "Failed to deserialize VARIANT type. "
+ + "The received data is not a valid JSON document.",
+ e);
+ }
+ }
+ };
+ }
+
private static DeserializationRuntimeConverter createEndiannessAwareConverter(
final boolean isBigEndian,
final MemorySegmentConverter bigEndianConverter,
@@ -302,6 +340,7 @@ private static DataLengthValidator createDataLengthValidator(LogicalType type) {
case VARBINARY:
case BINARY:
case RAW:
+ case VARIANT:
return data -> {};
case BOOLEAN:
return createDataLengthValidator(1, "BOOLEAN");
diff --git a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatFactory.java b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatFactory.java
index 05b52cf2c94e4e..61adff04473ff7 100644
--- a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatFactory.java
+++ b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatFactory.java
@@ -150,7 +150,8 @@ public ChangelogMode getChangelogMode() {
LogicalTypeRoot.INTEGER,
LogicalTypeRoot.BIGINT,
LogicalTypeRoot.FLOAT,
- LogicalTypeRoot.DOUBLE);
+ LogicalTypeRoot.DOUBLE,
+ LogicalTypeRoot.VARIANT);
/** Checks the given field type is supported. */
private static void checkFieldType(LogicalType fieldType) {
diff --git a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatSerializationSchema.java b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatSerializationSchema.java
index 2f16dee5caa210..bd27d4c2ad3948 100644
--- a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatSerializationSchema.java
+++ b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/formats/raw/RawFormatSerializationSchema.java
@@ -26,6 +26,8 @@
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.RawType;
+import org.apache.flink.types.variant.Variant;
+import org.apache.flink.types.variant.VariantTypeException;
import javax.annotation.Nullable;
@@ -89,7 +91,7 @@ public byte[] serialize(RowData row) {
byte[] result = Arrays.copyOf(valueBytes, valueBytes.length + delimiterBytes.length);
System.arraycopy(delimiterBytes, 0, result, valueBytes.length, delimiterBytes.length);
return result;
- } catch (IOException e) {
+ } catch (IOException | VariantTypeException e) {
throw new RuntimeException("Could not serialize row '" + row + "'. ", e);
}
}
@@ -164,6 +166,9 @@ private SerializationRuntimeConverter createNotNullConverter(
case RAW:
return createRawValueConverter((RawType>) type);
+ case VARIANT:
+ return createVariantConverter(charsetName);
+
case BOOLEAN:
return row -> {
byte b = (byte) (row.getBoolean(0) ? 1 : 0);
@@ -220,6 +225,30 @@ public byte[] convert(RowData row) {
};
}
+ /**
+ * Creates a converter that renders the {@link Variant} as a JSON document. The result is
+ * value-lossless but not byte-lossless: whitespace and object key order are normalized.
+ */
+ private static SerializationRuntimeConverter createVariantConverter(final String charsetName) {
+ // this also checks the charsetName is valid
+ Charset.forName(charsetName);
+
+ return new SerializationRuntimeConverter() {
+ private static final long serialVersionUID = 1L;
+ private transient Charset charset;
+
+ @Override
+ public void open() {
+ charset = Charset.forName(charsetName);
+ }
+
+ @Override
+ public byte[] convert(RowData row) {
+ return row.getVariant(0).toJson().getBytes(charset);
+ }
+ };
+ }
+
@SuppressWarnings("unchecked")
private static SerializationRuntimeConverter createRawValueConverter(RawType> rawType) {
final TypeSerializer