Skip to content

Commit 156d20f

Browse files
committed
Additional work wrt #35, #38, regarding handling of root-level Array values (which now work)
1 parent af1e155 commit 156d20f

File tree

5 files changed

+216
-28
lines changed

5 files changed

+216
-28
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/ArrayReader.java

+26-25
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,10 @@ public JsonToken nextToken() throws IOException
9090
switch (_state) {
9191
case STATE_START:
9292
_parser.setAvroContext(this);
93+
_index = 0;
9394
_count = _decoder.readArrayStart();
9495
_state = (_count > 0) ? STATE_ELEMENTS : STATE_END;
95-
{
96-
JsonToken t = JsonToken.START_ARRAY;
97-
_currToken = t;
98-
return t;
99-
}
96+
return (_currToken = JsonToken.START_ARRAY);
10097
case STATE_ELEMENTS:
10198
if (_index < _count) {
10299
break;
@@ -107,13 +104,18 @@ public JsonToken nextToken() throws IOException
107104
}
108105
// otherwise, we are done: fall through
109106
case STATE_END:
110-
_state = STATE_DONE;
111-
_parser.setAvroContext(getParent());
112-
{
113-
JsonToken t = JsonToken.END_ARRAY;
114-
_currToken = t;
115-
return t;
107+
final AvroReadContext parent = getParent();
108+
// as per [dataformats-binary#38], may need to reset, instead of bailing out
109+
if (parent.inRoot()) {
110+
if (!_decoder.isEnd()) {
111+
_index = 0;
112+
_state = STATE_START;
113+
return (_currToken = JsonToken.END_ARRAY);
114+
}
116115
}
116+
_state = STATE_DONE;
117+
_parser.setAvroContext(parent);
118+
return (_currToken = JsonToken.END_ARRAY);
117119
case STATE_DONE:
118120
default:
119121
throwIllegalState(_state);
@@ -157,11 +159,7 @@ public JsonToken nextToken() throws IOException
157159
_parser.setAvroContext(this);
158160
_count = _decoder.readArrayStart();
159161
_state = (_count > 0) ? STATE_ELEMENTS : STATE_END;
160-
{
161-
JsonToken t = JsonToken.START_ARRAY;
162-
_currToken = t;
163-
return t;
164-
}
162+
return (_currToken = JsonToken.START_ARRAY);
165163
case STATE_ELEMENTS:
166164
if (_index < _count) {
167165
break;
@@ -172,23 +170,26 @@ public JsonToken nextToken() throws IOException
172170
}
173171
// otherwise, we are done: fall through
174172
case STATE_END:
175-
_state = STATE_DONE;
176-
_parser.setAvroContext(getParent());
177-
{
178-
JsonToken t = JsonToken.END_ARRAY;
179-
_currToken = t;
180-
return t;
173+
final AvroReadContext parent = getParent();
174+
// as per [dataformats-binary#38], may need to reset, instead of bailing out
175+
if (parent.inRoot()) {
176+
if (!_decoder.isEnd()) {
177+
_index = 0;
178+
_state = STATE_START;
179+
return (_currToken = JsonToken.END_ARRAY);
180+
}
181181
}
182+
_state = STATE_DONE;
183+
_parser.setAvroContext(parent);
184+
return (_currToken = JsonToken.END_ARRAY);
182185
case STATE_DONE:
183186
default:
184187
throwIllegalState(_state);
185188
}
186189
++_index;
187190
AvroStructureReader r = _elementReader.newReader(this, _parser, _decoder);
188191
_parser.setAvroContext(r);
189-
JsonToken t = r.nextToken();
190-
_currToken = t;
191-
return t;
192+
return (_currToken = r.nextToken());
192193
}
193194
}
194195
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.fasterxml.jackson.dataformat.avro;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
7+
import com.fasterxml.jackson.core.*;
8+
9+
import com.fasterxml.jackson.databind.*;
10+
11+
public class ArrayTest extends AvroTestBase
12+
{
13+
private final AvroMapper MAPPER = getMapper();
14+
15+
// Simple test for a single array
16+
public void testRootStringArray() throws Exception
17+
{
18+
AvroSchema schema = getStringArraySchema();
19+
List<String> input = Arrays.asList("foo", "bar");
20+
21+
byte[] b = MAPPER.writer(schema).writeValueAsBytes(input);
22+
23+
// writing's good (probably), let's read. First as List:
24+
List<String> result = MAPPER.readerFor(List.class)
25+
.with(schema)
26+
.readValue(b);
27+
assertNotNull(result);
28+
assertEquals(2, result.size());
29+
assertEquals(input.get(0), result.get(0));
30+
assertEquals(input.get(1), result.get(1));
31+
32+
// then as String array
33+
String[] array = MAPPER.readerFor(String[].class)
34+
.with(schema)
35+
.readValue(b);
36+
assertNotNull(array);
37+
assertEquals(2, array.length);
38+
assertEquals(input.get(0), array[0]);
39+
assertEquals(input.get(1), array[1]);
40+
}
41+
42+
// And more complex: sequence of (String) arrays
43+
public void testStringArraySequence() throws Exception
44+
{
45+
AvroSchema schema = getStringArraySchema();
46+
List<String> input1 = Arrays.asList("foo", "bar");
47+
List<String> input2 = Arrays.asList("foobar");
48+
String[] input3 = new String[] { "a", "b", "c"};
49+
50+
// First: write a sequence of 3 root-level Employee Objects
51+
52+
ByteArrayOutputStream b = new ByteArrayOutputStream();
53+
SequenceWriter sw = MAPPER.writer(schema)
54+
.writeValues(b);
55+
sw.write(input1);
56+
int curr = b.size();
57+
sw.write(input2);
58+
int diff = b.size() - curr;
59+
if (diff == 0) {
60+
fail("Should have output more bytes for second entry, did not, total: "+curr);
61+
}
62+
sw.write(input3);
63+
sw.close();
64+
65+
// 18-Jan-2017, tatu: This get bit tricky just because `readValues()` doesn't
66+
// quite know whether to advance cursor to START_ARRAY or not, and we must
67+
// instead prepare things... and use direct bind
68+
69+
JsonParser p = MAPPER.getFactory().createParser(b.toByteArray());
70+
p.setSchema(schema);
71+
72+
assertToken(JsonToken.START_ARRAY, p.nextToken());
73+
74+
List<?> result1 = MAPPER.readValue(p, List.class);
75+
_compare(input1, result1);
76+
77+
assertToken(JsonToken.START_ARRAY, p.nextToken());
78+
List<?> result2 = MAPPER.readValue(p, List.class);
79+
_compare(input2, result2);
80+
81+
assertToken(JsonToken.START_ARRAY, p.nextToken());
82+
List<?> result3 = MAPPER.readValue(p, List.class);
83+
_compare(Arrays.asList(input3), result3);
84+
85+
assertNull(p.nextToken());
86+
p.close();
87+
}
88+
89+
// And the ultimate case of sequence of arrays of records
90+
public void testEmployeeArraySequence() throws Exception
91+
{
92+
AvroSchema schema = MAPPER.schemaFrom(EMPLOYEE_ARRAY_SCHEMA_JSON);
93+
94+
Employee boss = new Employee("Bossman", 55, new String[] { "[email protected]" }, null);
95+
Employee peon1 = new Employee("Worker#1", 24, new String[] { "[email protected]" }, boss);
96+
Employee peon2 = new Employee("Worker#2", 43, new String[] { "[email protected]" }, boss);
97+
98+
// First: write a sequence of 3 root-level Employee Objects
99+
100+
ByteArrayOutputStream b = new ByteArrayOutputStream();
101+
SequenceWriter sw = MAPPER.writer(schema)
102+
.writeValues(b);
103+
sw.write(new Employee[] { boss, peon1, peon2 });
104+
int curr = b.size();
105+
sw.write(new Employee[] { peon2, boss });
106+
int diff = b.size() - curr;
107+
if (diff == 0) {
108+
fail("Should have output more bytes for second entry, did not, total: "+curr);
109+
}
110+
sw.close();
111+
112+
// 18-Jan-2017, tatu: This get bit tricky just because `readValues()` doesn't
113+
// quite know whether to advance cursor to START_ARRAY or not, and we must
114+
// instead prepare things... and use direct bind
115+
116+
JsonParser p = MAPPER.getFactory().createParser(b.toByteArray());
117+
p.setSchema(schema);
118+
119+
assertToken(JsonToken.START_ARRAY, p.nextToken());
120+
121+
Employee[] result1 = MAPPER.readValue(p, Employee[].class);
122+
assertEquals(3, result1.length);
123+
assertEquals("Bossman", result1[0].name);
124+
assertEquals("Worker#2", result1[2].name);
125+
126+
assertToken(JsonToken.START_ARRAY, p.nextToken());
127+
Employee[] result2 = MAPPER.readValue(p, Employee[].class);
128+
assertEquals(2, result2.length);
129+
assertEquals("Bossman", result2[1].name);
130+
131+
assertNull(p.nextToken());
132+
p.close();
133+
}
134+
135+
private void _compare(List<String> input, List<?> result) {
136+
assertEquals(input, result);
137+
}
138+
}

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

+34-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public abstract class AvroTestBase extends TestCase
1919
/* Test schemas
2020
/**********************************************************
2121
*/
22-
22+
2323
protected final String EMPLOYEE_SCHEMA_JSON = "{\n"
2424
+"\"type\": \"record\",\n"
2525
+"\"name\": \"Employee\",\n"
@@ -30,6 +30,31 @@ public abstract class AvroTestBase extends TestCase
3030
+" {\"name\": \"boss\", \"type\": [\"Employee\",\"null\"]}\n"
3131
+"]}";
3232

33+
protected final String STRING_ARRAY_SCHEMA_JSON = "{\n"
34+
+"\"name\": \"StringArray\",\n"
35+
+"\"type\": \"array\",\n"
36+
+"\"items\": \"string\"\n}";
37+
38+
protected final String STRING_MAP_SCHEMA_JSON = "{\n"
39+
+"\"name\": \"StringMap\",\n"
40+
+"\"type\": \"map\",\n"
41+
+"\"values\": \"string\"\n}";
42+
43+
protected final String EMPLOYEE_ARRAY_SCHEMA_JSON = aposToQuotes(
44+
"{"
45+
+"'name': 'EmployeeArray',\n"
46+
+"'type': 'array',\n"
47+
+"'items': {\n"
48+
+" 'type': 'record',\n"
49+
+" 'name': 'Employee',\n"
50+
+" 'fields': [\n"
51+
+" {'name': 'name', 'type': 'string'},\n"
52+
+" {'name': 'age', 'type': 'int'},\n"
53+
+" {'name': 'emails', 'type': {'type': 'array', 'items': 'string'}},\n"
54+
+" {'name': 'boss', 'type': ['Employee','null']}\n"
55+
+" ]}\n"
56+
+"}\n");
57+
3358
/*
3459
/**********************************************************
3560
/* Test classes
@@ -208,6 +233,14 @@ protected AvroSchema getEmployeeSchema() throws IOException {
208233
return _employeeSchema;
209234
}
210235

236+
protected AvroSchema getStringArraySchema() throws IOException {
237+
return getMapper().schemaFrom(STRING_ARRAY_SCHEMA_JSON);
238+
}
239+
240+
protected AvroSchema getStringMapSchema() throws IOException {
241+
return getMapper().schemaFrom(STRING_MAP_SCHEMA_JSON);
242+
}
243+
211244
protected AvroMapper getMapper() {
212245
if (_sharedMapper == null) {
213246
_sharedMapper = newMapper();

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

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.io.IOException;
44

5-
import com.fasterxml.jackson.dataformat.avro.*;
65
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator;
76

87
public class BinaryDataTest extends AvroTestBase

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void setStuff(Map<String,String> arg) {
3636
}
3737
}
3838

39-
public void testSimple() throws Exception
39+
public void testRecordWithMap() throws Exception
4040
{
4141
AvroMapper mapper = getMapper();
4242
AvroSchema schema = mapper.schemaFrom(MAP_SCHEMA_JSON);
@@ -140,4 +140,21 @@ public void testMapOrNull() throws Exception
140140
assertEquals(1, output.stuff.size());
141141
assertEquals("y", output.stuff.get("x"));
142142
}
143+
144+
// 18-Jan-2017, tatu: It would seem reasonable to support root-level Maps too,
145+
// since Records and Arrays work, but looks like there are some issues
146+
// regarding them so can't yet test
147+
148+
/*
149+
public void testRootStringMap() throws Exception
150+
{
151+
AvroMapper mapper = getMapper();
152+
AvroSchema schema = getStringMapSchema();
153+
Map<String,String> input = new LinkedHashMap<>();
154+
input.put("a", "1");
155+
input.put("b", "2");
156+
157+
byte[] b = mapper.writer(schema).writeValueAsBytes(input);
158+
}
159+
*/
143160
}

0 commit comments

Comments
 (0)