Skip to content

Commit c8cb146

Browse files
committed
support root-level maps
1 parent 1eb628f commit c8cb146

File tree

4 files changed

+88
-30
lines changed

4 files changed

+88
-30
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,12 @@ protected GenericArray<Object> _createArray(Schema schema)
146146

147147
protected AvroWriteContext _createObjectContext(Schema schema) throws JsonMappingException
148148
{
149-
if (schema.getType() == Schema.Type.UNION) {
149+
Type type = schema.getType();
150+
if (type == Schema.Type.UNION) {
150151
schema = _recordOrMapFromUnion(schema);
152+
type = schema.getType();
151153
}
152-
if (schema.getType() == Schema.Type.MAP) {
154+
if (type == Schema.Type.MAP) {
153155
return new MapWriteContext(this, _generator, schema);
154156
}
155157
return new ObjectWriteContext(this, _generator, _createRecord(schema));

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/MapWriteContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public final class MapWriteContext
1616
extends KeyValueContext
1717
{
1818
protected final Map<String,Object> _data;
19-
19+
2020
public MapWriteContext(AvroWriteContext parent, AvroGenerator generator, Schema schema)
2121
{
2222
super(parent, generator, schema);

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/RootContext.java

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ class RootContext
1313
extends AvroWriteContext
1414
{
1515
/**
16-
* We need to keep reference to the root value here.
17-
*<p>
18-
* TODO: What about Map values at root? Avro codec does not seem
19-
* able to support it via Generic API.
16+
* We need to keep reference to the root value here; either
17+
* <code>GenericContainer</code> or <code>Map</code> (yes,
18+
* Avro APIs are... odd).
2019
*/
21-
protected GenericContainer _rootValue;
22-
20+
protected Object _rootValue;
21+
22+
/**
23+
* Lazily created instance for encoding: reused in case of root value sequences.
24+
*/
25+
private NonBSGenericDatumWriter<Object> _writer;
26+
2327
public RootContext(AvroGenerator generator, Schema schema) {
2428
super(TYPE_ROOT, null, generator, schema);
2529
}
@@ -51,16 +55,21 @@ public final AvroWriteContext createChildObjectContext() throws JsonMappingExcep
5155
switch (_schema.getType()) {
5256
case RECORD:
5357
case UNION: // maybe
54-
break;
55-
case MAP:
56-
throw new UnsupportedOperationException("Root-level Maps not supported: Avro Codec has no way to create these");
58+
{
59+
GenericRecord rec = _createRecord(_schema);
60+
_rootValue = rec;
61+
return new ObjectWriteContext(this, _generator, rec);
62+
}
63+
case MAP: // used to not be supported
64+
{
65+
MapWriteContext ctxt = new MapWriteContext(this, _generator, _schema);
66+
_rootValue = ctxt.rawValue();
67+
return ctxt;
68+
}
5769
default:
58-
throw new IllegalStateException("Can not write START_OBJECT; schema type is "
59-
+_schema.getType());
6070
}
61-
GenericRecord rec = _createRecord(_schema);
62-
_rootValue = rec;
63-
return new ObjectWriteContext(this, _generator, rec);
71+
throw new IllegalStateException("Can not write START_OBJECT; schema type is "
72+
+_schema.getType());
6473
}
6574

6675
@Override
@@ -73,15 +82,10 @@ public void writeString(String value) {
7382
_reportError();
7483
}
7584

76-
/**
77-
* Lazily created instance for encoding: reused in case of root value sequences.
78-
*/
79-
private NonBSGenericDatumWriter<GenericContainer> _writer;
80-
8185
@Override
8286
public void complete(BinaryEncoder encoder) throws IOException {
8387
if (_writer == null) {
84-
_writer = new NonBSGenericDatumWriter<GenericContainer>(_schema);
88+
_writer = new NonBSGenericDatumWriter<Object>(_schema);
8589
}
8690
_writer.write(_rootValue, encoder);
8791
}

avro/src/test/java/com/fasterxml/jackson/dataformat/avro/MapTest.java

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package com.fasterxml.jackson.dataformat.avro;
22

3+
import static org.junit.Assert.assertArrayEquals;
4+
35
import java.io.ByteArrayOutputStream;
46
import java.util.*;
57

68
import com.fasterxml.jackson.core.JsonGenerator;
79
import com.fasterxml.jackson.core.JsonParser;
810
import com.fasterxml.jackson.core.JsonToken;
11+
import com.fasterxml.jackson.databind.MappingIterator;
12+
import com.fasterxml.jackson.databind.SequenceWriter;
13+
import com.fasterxml.jackson.dataformat.avro.AvroTestBase.Employee;
914

1015
public class MapTest extends AvroTestBase
1116
{
@@ -36,6 +41,8 @@ public void setStuff(Map<String,String> arg) {
3641
}
3742
}
3843

44+
private final AvroMapper MAPPER = getMapper();
45+
3946
public void testRecordWithMap() throws Exception
4047
{
4148
AvroMapper mapper = getMapper();
@@ -145,16 +152,61 @@ public void testMapOrNull() throws Exception
145152
// since Records and Arrays work, but looks like there are some issues
146153
// regarding them so can't yet test
147154

148-
/*
149155
public void testRootStringMap() throws Exception
150156
{
151-
AvroMapper mapper = getMapper();
152157
AvroSchema schema = getStringMapSchema();
153-
Map<String,String> input = new LinkedHashMap<>();
154-
input.put("a", "1");
155-
input.put("b", "2");
158+
Map<String,String> input = _map("a", "1", "b", "2");
159+
160+
byte[] b = MAPPER.writer(schema).writeValueAsBytes(input);
161+
Map<String,String> result = MAPPER.readerFor(Map.class)
162+
.with(schema)
163+
.readValue(b);
164+
assertEquals(2, result.size());
165+
assertEquals("1", result.get("a"));
166+
assertEquals("2", result.get("b"));
167+
}
168+
169+
public void testRootMapSequence() throws Exception
170+
{
171+
ByteArrayOutputStream b = new ByteArrayOutputStream(1000);
172+
AvroSchema schema = getStringMapSchema();
173+
Map<String,String> input1 = _map("a", "1", "b", "2");
174+
Map<String,String> input2 = _map("c", "3", "d", "4");
175+
176+
SequenceWriter sw = MAPPER.writerFor(Map.class)
177+
.with(schema)
178+
.writeValues(b);
179+
sw.write(input1);
180+
int curr = b.size();
181+
sw.write(input2);
182+
int diff = b.size() - curr;
183+
if (diff == 0) {
184+
fail("Should have output more bytes for second entry, did not, total: "+curr);
185+
}
186+
sw.close();
156187

157-
byte[] b = mapper.writer(schema).writeValueAsBytes(input);
188+
byte[] bytes = b.toByteArray();
189+
190+
assertNotNull(bytes);
191+
192+
MappingIterator<Map<String,String>> it = MAPPER.readerFor(Map.class)
193+
.with(schema)
194+
.readValues(bytes);
195+
assertTrue(it.hasNextValue());
196+
assertEquals(input1, it.nextValue());
197+
198+
assertTrue(it.hasNextValue());
199+
assertEquals(input2, it.nextValue());
200+
201+
assertFalse(it.hasNextValue());
202+
it.close();
203+
}
204+
205+
private Map<String,String> _map(String... stuff) {
206+
Map<String,String> map = new LinkedHashMap<String,String>();
207+
for (int i = 0, end = stuff.length; i < end; i += 2) {
208+
map.put(stuff[i], stuff[i+1]);
209+
}
210+
return map;
158211
}
159-
*/
160212
}

0 commit comments

Comments
 (0)