Skip to content

Commit fb29f23

Browse files
authored
Json serialization of nullable fields (#1341)
1 parent 0f06760 commit fb29f23

File tree

5 files changed

+509
-3
lines changed

5 files changed

+509
-3
lines changed

runtime/model-avro/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@
191191
<configuration>
192192
<excludes>
193193
<exclude>io/aklivity/zilla/runtime/model/avro/internal/types/**/*.class</exclude>
194+
<exclude>org/apache/avro/**/*.class</exclude>
194195
</excludes>
195196
<rules>
196197
<rule>

runtime/model-avro/src/main/java/io/aklivity/zilla/runtime/model/avro/internal/AvroReadConverterHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.avro.generic.GenericDatumReader;
2828
import org.apache.avro.generic.GenericDatumWriter;
2929
import org.apache.avro.generic.GenericRecord;
30+
import org.apache.avro.io.CanonicalJsonEncoder;
3031
import org.apache.avro.io.JsonEncoder;
3132

3233
import io.aklivity.zilla.runtime.engine.EngineContext;
@@ -191,7 +192,7 @@ private void deserializeRecord(
191192
expandable.wrap(expandable.buffer());
192193
record = reader.read(record, decoderFactory.binaryDecoder(in, decoder));
193194
Schema schema = record.getSchema();
194-
JsonEncoder out = encoderFactory.jsonEncoder(schema, expandable);
195+
JsonEncoder out = new CanonicalJsonEncoder(schema, expandable);
195196
writer.write(record, out);
196197
out.flush();
197198

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2021-2024 Aklivity Inc
3+
*
4+
* Licensed under the Aklivity Community License (the "License"); you may not use
5+
* this file except in compliance with the License. You may obtain a copy of the
6+
* License at
7+
*
8+
* https://www.aklivity.io/aklivity-community-license/
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OF ANY KIND, either express or implied. See the License for the
13+
* specific language governing permissions and limitations under the License.
14+
*/
15+
package org.apache.avro.io;
16+
17+
import java.io.IOException;
18+
import java.io.OutputStream;
19+
20+
import org.apache.avro.Schema;
21+
import org.apache.avro.io.parsing.Parser;
22+
import org.apache.avro.io.parsing.Symbol;
23+
24+
import com.fasterxml.jackson.core.JsonGenerator;
25+
26+
/**
27+
* A derived encoder that does the skipping of fields that match the index. It also encodes unions of null and a single
28+
* type as a more normal key=value rather than key={type=value}.
29+
*
30+
* @author zfarkas
31+
*/
32+
public final class CanonicalJsonEncoder extends JsonEncoder
33+
{
34+
35+
36+
public CanonicalJsonEncoder(
37+
final Schema sc,
38+
final OutputStream out) throws IOException
39+
{
40+
super(sc, out);
41+
}
42+
43+
public CanonicalJsonEncoder(
44+
final Schema sc,
45+
final OutputStream out,
46+
final boolean pretty) throws IOException
47+
{
48+
super(sc, out, pretty);
49+
}
50+
51+
public CanonicalJsonEncoder(
52+
final Schema sc,
53+
final JsonGenerator out) throws IOException
54+
{
55+
super(sc, out);
56+
}
57+
58+
public Parser getParser()
59+
{
60+
return parser;
61+
}
62+
63+
public static boolean isNullableSingle(
64+
final Symbol.Alternative top)
65+
{
66+
return top.size() == 2 && ("null".equals(top.getLabel(0)) || "null".equals(top.getLabel(1)));
67+
}
68+
69+
public static String getNullableSingle(
70+
final Symbol.Alternative top)
71+
{
72+
final String label = top.getLabel(0);
73+
return "null".equals(label) ? top.getLabel(1) : label;
74+
}
75+
76+
/**
77+
* Overwrite this function to optime json decoding of union {null, type}.
78+
*
79+
* @param unionIndex
80+
* @throws IOException
81+
*/
82+
83+
@Override
84+
public void writeIndex(
85+
final int unionIndex) throws IOException
86+
{
87+
parser.advance(Symbol.UNION);
88+
Symbol.Alternative top = (Symbol.Alternative) parser.popSymbol();
89+
Symbol symbol = top.getSymbol(unionIndex);
90+
if (symbol != Symbol.NULL && !isNullableSingle(top))
91+
{
92+
out.writeStartObject();
93+
out.writeFieldName(top.getLabel(unionIndex));
94+
parser.pushSymbol(Symbol.UNION_END);
95+
}
96+
parser.pushSymbol(symbol);
97+
}
98+
99+
}

0 commit comments

Comments
 (0)