SOLR-12362: Uploading docs in JSON now supports child documents as field values

This commit is contained in:
user 2018-06-14 12:14:49 -04:00 committed by David Smiley
parent a26a1bb249
commit 21fe4164de
6 changed files with 354 additions and 169 deletions

View File

@ -74,6 +74,10 @@ New Features
* SOLR-12474: Add an UpdateRequest Object that implements RequestWriter.ContentWriter (noble)
* SOLR-12362: Uploading docs in JSON now supports child documents as field values, thus providing a label to the
relationship instead of the current "anonymous" relationship. Use of this experimental feature requires
anonChildDocs=false parameter. (Moshe Bla, David Smiley)
Bug Fixes
----------------------

View File

@ -93,11 +93,16 @@ public class JsonLoader extends ContentStreamLoader {
protected JSONParser parser;
protected final int commitWithin;
protected final boolean overwrite;
protected final boolean anonChildDoc;
/**
* {@link CommonParams#ANONYMOUS_CHILD_DOCS} Defaults to true.
*/
public SingleThreadedJsonLoader(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor processor) {
this.processor = processor;
this.req = req;
this.rsp = rsp;
this.anonChildDoc = req.getParams().getBool(CommonParams.ANONYMOUS_CHILD_DOCS, true);
commitWithin = req.getParams().getInt(UpdateParams.COMMIT_WITHIN, -1);
overwrite = req.getParams().getBool(UpdateParams.OVERWRITE, true);
@ -248,14 +253,28 @@ public class JsonLoader extends ContentStreamLoader {
private SolrInputDocument buildDoc(Map<String, Object> m) {
SolrInputDocument result = new SolrInputDocument();
for (Map.Entry<String, Object> e : m.entrySet()) {
if (e.getKey() == null) {// special case. JsonRecordReader emits child docs with null key
if (mapEntryIsChildDoc(e.getValue())) { // parse child documents
if (e.getValue() instanceof List) {
List value = (List) e.getValue();
for (Object o : value) {
if (o instanceof Map) result.addChildDocument(buildDoc((Map) o));
if (o instanceof Map) {
if (anonChildDoc) {
result.addChildDocument(buildDoc((Map) o));
} else {
// retain the value as a list, even if the list contains a single value.
if(!result.containsKey(e.getKey())) {
result.setField(e.getKey(), new ArrayList<>(1));
}
result.addField(e.getKey(), buildDoc((Map) o));
}
}
}
} else if (e.getValue() instanceof Map) {
result.addChildDocument(buildDoc((Map) e.getValue()));
if (anonChildDoc) {
result.addChildDocument(buildDoc((Map) e.getValue()));
} else {
result.addField(e.getKey(), buildDoc((Map) e.getValue()));
}
}
} else {
result.setField(e.getKey(), e.getValue());
@ -530,7 +549,7 @@ public class JsonLoader extends ContentStreamLoader {
}
String fieldName = parser.getString();
if (fieldName.equals(JsonLoader.CHILD_DOC_KEY)) {
if (anonChildDoc && fieldName.equals(JsonLoader.CHILD_DOC_KEY)) {
ev = parser.nextEvent();
assertEvent(ev, JSONParser.ARRAY_START);
while ((ev = parser.nextEvent()) != JSONParser.ARRAY_END) {
@ -554,84 +573,79 @@ public class JsonLoader extends ContentStreamLoader {
private void parseFieldValue(SolrInputField sif) throws IOException {
int ev = parser.nextEvent();
if (ev == JSONParser.OBJECT_START) {
parseExtendedFieldValue(sif, ev);
parseExtendedFieldValue(ev, sif);
} else {
Object val = parseNormalFieldValue(ev, sif.getName());
Object val = parseNormalFieldValue(ev, sif);
sif.setValue(val);
}
}
private void parseExtendedFieldValue(SolrInputField sif, int ev) throws IOException {
/**
* A method to either extract an index time boost (deprecated), a map for atomic update, or a child document.
* firstly, a solr document SolrInputDocument constructed. It is then determined whether the document is indeed a childDocument(if it has a unique field).
* If so, it is added.
* Otherwise the document is looped over as a map, and is then parsed as an Atomic Update if that is the case.
* @param ev json parser event
* @param sif input field to add value to.
* @throws IOException in case of parsing exception.
*/
private void parseExtendedFieldValue(int ev, SolrInputField sif) throws IOException {
assert ev == JSONParser.OBJECT_START;
SolrInputDocument extendedSolrDocument = parseDoc(ev);
if (isChildDoc(extendedSolrDocument)) {
sif.addValue(extendedSolrDocument);
return;
}
Object normalFieldValue = null;
Map<String, Object> extendedInfo = null;
for (; ; ) {
ev = parser.nextEvent();
switch (ev) {
case JSONParser.STRING:
String label = parser.getString();
if ("boost".equals(label)) {
ev = parser.nextEvent();
if (ev != JSONParser.NUMBER &&
ev != JSONParser.LONG &&
ev != JSONParser.BIGNUMBER) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Boost should have number. "
+ "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + sif.getName());
}
for (SolrInputField entry: extendedSolrDocument) {
Object val = entry.getValue();
String label = entry.getName();
if ("boost".equals(label)) {
Object boostVal = val;
if (!(boostVal instanceof Double)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Boost should have number. "
+ "Unexpected value: " + boostVal.toString() + "field=" + label);
}
String message = "Ignoring field boost: " + parser.getDouble() + " as index-time boosts are not supported anymore";
if (WARNED_ABOUT_INDEX_TIME_BOOSTS.compareAndSet(false, true)) {
log.warn(message);
} else {
log.debug(message);
}
} else if ("value".equals(label)) {
normalFieldValue = parseNormalFieldValue(parser.nextEvent(), sif.getName());
} else {
// If we encounter other unknown map keys, then use a map
if (extendedInfo == null) {
extendedInfo = new HashMap<>(2);
}
// for now, the only extended info will be field values
// we could either store this as an Object or a SolrInputField
Object val = parseNormalFieldValue(parser.nextEvent(), sif.getName());
extendedInfo.put(label, val);
}
break;
case JSONParser.OBJECT_END:
if (extendedInfo != null) {
if (normalFieldValue != null) {
extendedInfo.put("value", normalFieldValue);
}
sif.setValue(extendedInfo);
} else {
sif.setValue(normalFieldValue);
}
return;
default:
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing JSON extended field value. "
+ "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + sif.getName());
String message = "Ignoring field boost: " + boostVal.toString() + " as index-time boosts are not supported anymore";
if (WARNED_ABOUT_INDEX_TIME_BOOSTS.compareAndSet(false, true)) {
log.warn(message);
} else {
log.debug(message);
}
} else if ("value".equals(label)) {
normalFieldValue = val;
} else {
// If we encounter other unknown map keys, then use a map
if (extendedInfo == null) {
extendedInfo = new HashMap<>(2);
}
// for now, the only extended info will be field values
// we could either store this as an Object or a SolrInputField
extendedInfo.put(label, val);
}
if (extendedInfo != null) {
if (normalFieldValue != null) {
extendedInfo.put("value", normalFieldValue);
}
sif.setValue(extendedInfo);
} else {
sif.setValue(normalFieldValue);
}
}
}
private Object parseNormalFieldValue(int ev, String fieldName) throws IOException {
if (ev == JSONParser.ARRAY_START) {
List<Object> val = parseArrayFieldValue(ev, fieldName);
return val;
} else {
Object val = parseSingleFieldValue(ev, fieldName);
return val;
}
private Object parseNormalFieldValue(int ev, SolrInputField sif) throws IOException {
return ev == JSONParser.ARRAY_START ? parseArrayFieldValue(ev, sif): parseSingleFieldValue(ev, sif);
}
private Object parseSingleFieldValue(int ev, String fieldName) throws IOException {
private Object parseSingleFieldValue(int ev, SolrInputField sif) throws IOException {
switch (ev) {
case JSONParser.STRING:
return parser.getString();
@ -647,15 +661,18 @@ public class JsonLoader extends ContentStreamLoader {
parser.getNull();
return null;
case JSONParser.ARRAY_START:
return parseArrayFieldValue(ev, fieldName);
return parseArrayFieldValue(ev, sif);
case JSONParser.OBJECT_START:
parseExtendedFieldValue(ev, sif);
return sif.getValue();
default:
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing JSON field value. "
+ "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + fieldName);
+ "Unexpected " + JSONParser.getEventString(ev) + " at [" + parser.getPosition() + "], field=" + sif.getName());
}
}
private List<Object> parseArrayFieldValue(int ev, String fieldName) throws IOException {
private List<Object> parseArrayFieldValue(int ev, SolrInputField sif) throws IOException {
assert ev == JSONParser.ARRAY_START;
ArrayList lst = new ArrayList(2);
@ -664,10 +681,24 @@ public class JsonLoader extends ContentStreamLoader {
if (ev == JSONParser.ARRAY_END) {
return lst;
}
Object val = parseSingleFieldValue(ev, fieldName);
Object val = parseSingleFieldValue(ev, sif);
lst.add(val);
sif.setValue(null);
}
}
private boolean isChildDoc(SolrInputDocument extendedFieldValue) {
return extendedFieldValue.containsKey(req.getSchema().getUniqueKeyField().getName());
}
private boolean mapEntryIsChildDoc(Object val) {
if(val instanceof List) {
List listVal = (List) val;
if (listVal.size() == 0) return false;
return listVal.get(0) instanceof Map;
}
return val instanceof Map;
}
}
private static Object changeChildDoc(Object o) {

View File

@ -16,6 +16,11 @@
*/
package org.apache.solr.handler;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
@ -32,10 +37,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.noggit.ObjectBuilder;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import static org.apache.solr.common.params.CommonParams.ANONYMOUS_CHILD_DOCS;
public class JsonLoaderTest extends SolrTestCaseJ4 {
@ -411,6 +413,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
" \"id\": \"1.2\",\n" +
" \"name\": \"i am the 2nd child\",\n" +
" \"cat\": \"child\",\n" +
" \"test_s\": \"test-new-label\",\n" +
" \"grandchildren\": [\n" +
" {\n" +
" \"id\": \"1.2.1\",\n" +
@ -432,63 +435,87 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
"f", "name:/children/grandchildren/name",
"f", "cat:/children/grandchildren/cat"};
@Test
public void testFewParentsAnonymousJsonDoc() throws Exception {
String json = PARENT_TWO_CHILDREN_JSON;
assertFewParents(json, true);
}
@Test
public void testFewParentsJsonDoc() throws Exception {
String json = PARENT_TWO_CHILDREN_JSON;
assertFewParents(json, false);
}
private void assertFewParents(String json, boolean anonChildDocsFlag) throws Exception {
SolrQueryRequest req;
SolrQueryResponse rsp;
BufferingRequestProcessor p;
JsonLoader loader;
{ //multichild test case
final boolean array = random().nextBoolean();
StringBuilder b = new StringBuilder();
JsonLoader loader;//multichild test case
final boolean array = random().nextBoolean();
StringBuilder b = new StringBuilder();
if (array) {
b.append("[");
}
final int passes = atLeast(2);
for (int i=1;i<=passes;i++){
b.append(json.replace("1",""+i));
if (array) {
b.append("[");
b.append(i<passes ? "," :"]");
}
final int passes = atLeast(2);
for (int i=1;i<=passes;i++){
b.append(json.replace("1",""+i));
if (array) {
b.append(i<passes ? "," :"]");
}
}
req = req(PARENT_TWO_CHILDREN_PARAMS, ANONYMOUS_CHILD_DOCS, Boolean.toString(anonChildDocsFlag));
req.getContext().put("path", "/update/json/docs");
rsp = new SolrQueryResponse();
p = new BufferingRequestProcessor(null);
loader = new JsonLoader();
loader.load(req, rsp, new ContentStreamBase.StringStream(b.toString()), p);
for (int i=1; i<=passes; i++){
final int ii = i;
UnaryOperator<String> s = (v)-> v.replace("1",""+ii);
final SolrInputDocument parent = p.addCommands.get(i-1).solrDoc;
assertOnlyValue(s.apply("1"), parent,"id");
assertOnlyValue("i am the parent", parent, "name");
assertOnlyValue("parent", parent, "cat");
List<SolrInputDocument> childDocs1;
if(anonChildDocsFlag) {
childDocs1 = parent.getChildDocuments();
} else {
childDocs1 = (List) ((parent.getField("children")).getValue());
}
req = req(PARENT_TWO_CHILDREN_PARAMS);
req.getContext().put("path", "/update/json/docs");
rsp = new SolrQueryResponse();
p = new BufferingRequestProcessor(null);
loader = new JsonLoader();
loader.load(req, rsp, new ContentStreamBase.StringStream(b.toString()), p);
for (int i=1; i<=passes; i++){
final int ii = i;
UnaryOperator<String> s = (v)-> v.replace("1",""+ii);
final SolrInputDocument parent = p.addCommands.get(i-1).solrDoc;
assertOnlyValue(s.apply("1"), parent,"id");
assertOnlyValue("i am the parent", parent, "name");
assertOnlyValue("parent", parent, "cat");
assertEquals(2, parent.getChildDocuments().size());
{
final SolrInputDocument child1 = parent.getChildDocuments().get(0);
assertOnlyValue(s.apply("1.1"), child1, "id");
assertOnlyValue(s.apply("i am the 1st child"), child1, "name");
assertOnlyValue("child", child1,"cat");
}
{
final SolrInputDocument child2 = parent.getChildDocuments().get(1);
assertOnlyValue(s.apply("1.2"), child2, "id");
assertOnlyValue("i am the 2nd child", child2, "name");
assertOnlyValue("child", child2, "cat");
assertEquals(1, child2.getChildDocuments().size());
final SolrInputDocument grandChild = child2.getChildDocuments().get(0);
assertOnlyValue(s.apply("1.2.1"), grandChild,"id");
assertOnlyValue("i am the grandchild", grandChild, "name");
assertOnlyValue("grandchild", grandChild, "cat");
assertEquals(2, childDocs1.size());
{
final SolrInputDocument child1 = childDocs1.get(0);
assertOnlyValue(s.apply("1.1"), child1, "id");
assertOnlyValue(s.apply("i am the 1st child"), child1, "name");
assertOnlyValue("child", child1,"cat");
}
{
final SolrInputDocument child2 = childDocs1.get(1);
assertOnlyValue(s.apply("1.2"), child2, "id");
assertOnlyValue("i am the 2nd child", child2, "name");
assertOnlyValue("test-new-label", child2, "test_s");
assertOnlyValue("child", child2, "cat");
List<SolrInputDocument> childDocs2;
if(anonChildDocsFlag) {
childDocs2 = child2.getChildDocuments();
} else {
childDocs2 = (List) ((child2.getField("grandchildren")).getValue());
}
assertEquals(1, childDocs2.size());
final SolrInputDocument grandChild = childDocs2.get(0);
assertOnlyValue(s.apply("1.2.1"), grandChild,"id");
assertOnlyValue("i am the grandchild", grandChild, "name");
assertOnlyValue("grandchild", grandChild, "cat");
}
}
}
private static void assertOnlyValue(String expected, SolrInputDocument doc, String field) {
assertEquals(Collections.singletonList(expected), doc.getFieldValues(field));
}
@ -790,53 +817,69 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
req.close();
}
private static final String SIMPLE_JSON_CHILD_DOCS = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"id\": \"1\",\n" +
" \"_childDocuments_\": [\n" +
" {\n" +
" \"id\": \"2\"\n" +
" },\n" +
" {\n" +
" \"id\": \"3\",\n" +
" \"foo_i\": [666,777]\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
@Test
public void testSimpleAnonymousChildDocs() throws Exception {
checkTwoAnonymousChildDocs(SIMPLE_JSON_CHILD_DOCS);
}
@Test
public void testSimpleChildDocs() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"id\": \"1\",\n" +
" \"_childDocuments_\": [\n" +
" {\n" +
" \"id\": \"2\"\n" +
" },\n" +
" {\n" +
" \"id\": \"3\",\n" +
" \"foo_i\": [666,777]\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
checkTwoChildDocs(str);
checkTwoAnonymousChildDocs(SIMPLE_JSON_CHILD_DOCS, false);
}
private static final String DUP_KEYS_JSON_CHILD_DOCS = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"_childDocuments_\": [\n" +
" {\n" +
" \"id\": \"2\"\n" +
" }\n" +
" ],\n" +
" \"id\": \"1\",\n" +
" \"_childDocuments_\": [\n" +
" {\n" +
" \"id\": \"3\",\n" +
" \"foo_i\": 666,\n" +
" \"foo_i\": 777\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
@Test
public void testDupKeysAnonymousChildDocs() throws Exception {
checkTwoAnonymousChildDocs(DUP_KEYS_JSON_CHILD_DOCS);
}
@Test
public void testDupKeysChildDocs() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"_childDocuments_\": [\n" +
" {\n" +
" \"id\": \"2\"\n" +
" }\n" +
" ],\n" +
" \"id\": \"1\",\n" +
" \"_childDocuments_\": [\n" +
" {\n" +
" \"id\": \"3\",\n" +
" \"foo_i\": 666,\n" +
" \"foo_i\": 777\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
checkTwoChildDocs(str);
checkTwoAnonymousChildDocs(DUP_KEYS_JSON_CHILD_DOCS, false);
}
private void checkTwoChildDocs(String rawJsonStr) throws Exception {
SolrQueryRequest req = req("commit","true");
private void checkTwoAnonymousChildDocs(String rawJsonStr) throws Exception {
checkTwoAnonymousChildDocs(rawJsonStr, true);
}
private void checkTwoAnonymousChildDocs(String rawJsonStr, boolean anonChildDocs) throws Exception {
SolrQueryRequest req = req("commit","true", ANONYMOUS_CHILD_DOCS, Boolean.toString(anonChildDocs));
SolrQueryResponse rsp = new SolrQueryResponse();
BufferingRequestProcessor p = new BufferingRequestProcessor(null);
JsonLoader loader = new JsonLoader();
@ -849,11 +892,20 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
SolrInputField f = d.getField( "id" );
assertEquals("1", f.getValue());
SolrInputDocument cd = d.getChildDocuments().get(0);
SolrInputDocument cd;
if (anonChildDocs) {
cd = d.getChildDocuments().get(0);
} else {
cd = (SolrInputDocument) (d.getField("_childDocuments_")).getFirstValue();
}
SolrInputField cf = cd.getField( "id" );
assertEquals("2", cf.getValue());
cd = d.getChildDocuments().get(1);
if (anonChildDocs) {
cd = d.getChildDocuments().get(1);
} else {
cd = (SolrInputDocument)((List)(d.getField("_childDocuments_")).getValue()).get(1);
}
cf = cd.getField( "id" );
assertEquals("3", cf.getValue());
cf = cd.getField( "foo_i" );
@ -865,7 +917,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
}
@Test
public void testEmptyChildDocs() throws Exception {
public void testEmptyAnonymousChildDocs() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
@ -893,7 +945,7 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
}
@Test
public void testGrandChildDocs() throws Exception {
public void testAnonymousGrandChildDocs() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
@ -946,5 +998,87 @@ public class JsonLoaderTest extends SolrTestCaseJ4 {
}
@Test
public void testChildDocs() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"id\": \"1\",\n" +
" \"children\": [\n" +
" {\n" +
" \"id\": \"2\",\n" +
" \"foo_s\": \"Yaz\"\n" +
" },\n" +
" {\n" +
" \"id\": \"3\",\n" +
" \"foo_s\": \"Bar\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
SolrQueryRequest req = req("commit","true");
SolrQueryResponse rsp = new SolrQueryResponse();
BufferingRequestProcessor p = new BufferingRequestProcessor(null);
JsonLoader loader = new JsonLoader();
loader.load(req, rsp, new ContentStreamBase.StringStream(str), p);
assertEquals( 1, p.addCommands.size() );
AddUpdateCommand add = p.addCommands.get(0);
SolrInputDocument one = add.solrDoc;
assertEquals("1", one.getFieldValue("id"));
List<SolrInputDocument> children = (List) one.getFieldValues("children");
SolrInputDocument two = children.get(0);
assertEquals("2", two.getFieldValue("id"));
assertEquals("Yaz", two.getFieldValue("foo_s"));
SolrInputDocument three = children.get(1);
assertEquals("3", three.getFieldValue("id"));
assertEquals("Bar", three.getFieldValue("foo_s"));
req.close();
}
@Test
public void testSingleRelationalChildDoc() throws Exception {
String str = "{\n" +
" \"add\": {\n" +
" \"doc\": {\n" +
" \"id\": \"1\",\n" +
" \"child1\": \n" +
" {\n" +
" \"id\": \"2\",\n" +
" \"foo_s\": \"Yaz\"\n" +
" },\n" +
" }\n" +
" }\n" +
"}";
SolrQueryRequest req = req("commit","true");
SolrQueryResponse rsp = new SolrQueryResponse();
BufferingRequestProcessor p = new BufferingRequestProcessor(null);
JsonLoader loader = new JsonLoader();
loader.load(req, rsp, new ContentStreamBase.StringStream(str), p);
assertEquals( 1, p.addCommands.size() );
AddUpdateCommand add = p.addCommands.get(0);
SolrInputDocument one = add.solrDoc;
assertEquals("1", one.getFieldValue("id"));
assertTrue(one.keySet().contains("child1"));
SolrInputDocument two = (SolrInputDocument) one.getField("child1").getValue();
assertEquals("2", two.getFieldValue("id"));
assertEquals("Yaz", two.getFieldValue("foo_s"));
req.close();
}
}

View File

@ -87,8 +87,8 @@ public class SolrInputField implements Iterable<Object>, Serializable
value = vals;
}
// Add the new values to a collection
if( v instanceof Iterable ) {
// Add the new values to a collection, if childDoc add as is without iteration
if( v instanceof Iterable && !(v instanceof SolrDocumentBase)) {
for( Object o : (Iterable<Object>)v ) {
vals.add( o );
}

View File

@ -290,5 +290,11 @@ public interface CommonParams {
String JSON_MIME = "application/json";
String JAVABIN_MIME = "application/javabin";
/**
* If set to true, child documents will be added as anonymous children into the _childDocuments list,
* else, child documents will be added to SolrInputDocument as field values according to their key name.
*/
String ANONYMOUS_CHILD_DOCS = "anonChildDocs";
}

View File

@ -288,13 +288,13 @@ public class JsonRecordReader {
event = parser.nextEvent();
if (event == EOF) break;
if (event == OBJECT_START) {
handleObjectStart(parser, handler, values, new Stack<>(), recordStarted, null);
handleObjectStart(parser, handler, new LinkedHashMap<>(), new Stack<>(), recordStarted, null);
} else if (event == ARRAY_START) {
for (; ; ) {
event = parser.nextEvent();
if (event == ARRAY_END) break;
if (event == OBJECT_START) {
handleObjectStart(parser, handler, values, new Stack<>(), recordStarted, null);
handleObjectStart(parser, handler, new LinkedHashMap<>(), new Stack<>(), recordStarted, null);
}
}
}
@ -350,6 +350,10 @@ public class JsonRecordReader {
event = parser.nextEvent();
if (event == ARRAY_END) break;
if (event == OBJECT_START) {
// if single item in array will still be added as array
if(!values.containsKey(name)) {
values.put(name, new ArrayList<>());
}
walkObject();
}
}
@ -359,7 +363,7 @@ public class JsonRecordReader {
void walkObject() throws IOException {
if (node.isChildRecord) {
node.handleObjectStart(parser,
(record, path) -> addChildDoc2ParentDoc(record, values),
(record, path) -> addChildDoc2ParentDoc(record, values, getPathSuffix(path)),
new LinkedHashMap<>(),
new Stack<>(),
true,
@ -438,18 +442,18 @@ public class JsonRecordReader {
}
}
private void addChildDoc2ParentDoc(Map<String, Object> record, Map<String, Object> values) {
private void addChildDoc2ParentDoc(Map<String, Object> record, Map<String, Object> values, String key) {
record = Utils.getDeepCopy(record, 2);
Object oldVal = values.get(null);
Object oldVal = values.get(key);
if (oldVal == null) {
values.put(null, record);
values.put(key, record);
} else if (oldVal instanceof List) {
((List) oldVal).add(record);
} else {
ArrayList l = new ArrayList();
l.add(oldVal);
l.add(record);
values.put(null, l);
values.put(key, l);
}
}
@ -486,6 +490,12 @@ public class JsonRecordReader {
values.put(fieldName, l);
}
// returns the last key of the path
private String getPathSuffix(String path) {
int indexOf = path.lastIndexOf("/");
if (indexOf == -1) return path;
return path.substring(indexOf + 1);
}
@Override
public String toString() {