Add XContentHelper.childBytes() method (#54287)
We have a number of places where we want to read a fairly complex object from XContent, but aren't interested in its contents; for example, mappings are often serialized and deserialized between several objects before they are actually built into a MappingMetaData object. This means that potentially large maps of maps are constructed several times, only to immediately be re-serialized again. This commit adds a new helper method to XContentHelper that reads the children of an xcontent object directly to a BytesReference, serialized via the same xcontenttype as the parent parser, avoiding the construction of intermediary maps or lists.
This commit is contained in:
parent
d9d11f6d16
commit
461f1307d6
|
@ -384,4 +384,23 @@ public class XContentHelper {
|
||||||
BytesRef br = bytes.toBytesRef();
|
BytesRef br = bytes.toBytesRef();
|
||||||
return XContentFactory.xContentType(br.bytes, br.offset, br.length);
|
return XContentFactory.xContentType(br.bytes, br.offset, br.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of an object as an unparsed BytesReference
|
||||||
|
*
|
||||||
|
* This is useful for things like mappings where we're copying bytes around but don't
|
||||||
|
* actually need to parse their contents, and so avoids building large maps of maps
|
||||||
|
* unnecessarily
|
||||||
|
*/
|
||||||
|
public static BytesReference childBytes(XContentParser parser) throws IOException {
|
||||||
|
if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
|
||||||
|
if (parser.nextToken() != XContentParser.Token.START_OBJECT) {
|
||||||
|
throw new XContentParseException(parser.getTokenLocation(),
|
||||||
|
"Expected [START_OBJECT] but got [" + parser.currentToken() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XContentBuilder builder = XContentBuilder.builder(parser.contentType().xContent());
|
||||||
|
builder.copyCurrentStructure(parser);
|
||||||
|
return BytesReference.bytes(builder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,12 @@
|
||||||
package org.elasticsearch.common.xcontent.support;
|
package org.elasticsearch.common.xcontent.support;
|
||||||
|
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.compress.CompressedXContent;
|
||||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
|
@ -105,4 +107,127 @@ public class XContentHelperTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testChildBytes() throws IOException {
|
||||||
|
|
||||||
|
for (XContentType xContentType : XContentType.values()) {
|
||||||
|
|
||||||
|
XContentBuilder builder = XContentBuilder.builder(xContentType.xContent());
|
||||||
|
builder.startObject().startObject("level1");
|
||||||
|
builder.startObject("level2")
|
||||||
|
.startObject("object").field("text", "string").field("number", 10).endObject()
|
||||||
|
.startObject("object2").field("boolean", true).nullField("null")
|
||||||
|
.startArray("array_of_strings").value("string1").value("string2").endArray().endObject().endObject();
|
||||||
|
builder.field("field", "value");
|
||||||
|
builder.endObject().endObject();
|
||||||
|
BytesReference input = BytesReference.bytes(builder);
|
||||||
|
|
||||||
|
BytesReference bytes;
|
||||||
|
try (XContentParser parser = xContentType.xContent()
|
||||||
|
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, input.streamInput())) {
|
||||||
|
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("level2", parser.currentName());
|
||||||
|
// Extract everything under 'level2' as a bytestream
|
||||||
|
bytes = XContentHelper.childBytes(parser);
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("field", parser.currentName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// now parse the contents of 'level2'
|
||||||
|
try (XContentParser parser = xContentType.xContent()
|
||||||
|
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, bytes.streamInput())) {
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("object", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("text", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.VALUE_STRING, parser.nextToken());
|
||||||
|
assertEquals("string", parser.text());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("number", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.VALUE_NUMBER, parser.nextToken());
|
||||||
|
assertEquals(10, parser.numberValue());
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("object2", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("boolean", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.VALUE_BOOLEAN, parser.nextToken());
|
||||||
|
assertTrue(parser.booleanValue());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("null", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.VALUE_NULL, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("array_of_strings", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.START_ARRAY, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.VALUE_STRING, parser.nextToken());
|
||||||
|
assertEquals("string1", parser.text());
|
||||||
|
assertEquals(XContentParser.Token.VALUE_STRING, parser.nextToken());
|
||||||
|
assertEquals("string2", parser.text());
|
||||||
|
assertEquals(XContentParser.Token.END_ARRAY, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
|
assertNull(parser.nextToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEmbeddedObject() throws IOException {
|
||||||
|
// Need to test this separately as XContentType.JSON never produces VALUE_EMBEDDED_OBJECT
|
||||||
|
XContentBuilder builder = XContentBuilder.builder(XContentType.CBOR.xContent());
|
||||||
|
builder.startObject().startObject("root");
|
||||||
|
CompressedXContent embedded = new CompressedXContent("{\"field\":\"value\"}");
|
||||||
|
builder.field("bytes", embedded.compressed());
|
||||||
|
builder.endObject().endObject();
|
||||||
|
BytesReference bytes = BytesReference.bytes(builder);
|
||||||
|
|
||||||
|
BytesReference inner;
|
||||||
|
try (XContentParser parser = XContentType.CBOR.xContent().createParser(
|
||||||
|
NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, bytes.streamInput())) {
|
||||||
|
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
inner = XContentHelper.childBytes(parser);
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
|
assertNull(parser.nextToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
try (XContentParser parser = XContentType.CBOR.xContent().createParser(
|
||||||
|
NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, inner.streamInput())) {
|
||||||
|
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
assertEquals("bytes", parser.currentName());
|
||||||
|
assertEquals(XContentParser.Token.VALUE_EMBEDDED_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(embedded, new CompressedXContent(parser.binaryValue()));
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
|
assertNull(parser.nextToken());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEmptyChildBytes() throws IOException {
|
||||||
|
|
||||||
|
String inputJson = "{ \"mappings\" : {} }";
|
||||||
|
try (XContentParser parser = XContentType.JSON.xContent()
|
||||||
|
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, inputJson)) {
|
||||||
|
|
||||||
|
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||||
|
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||||
|
BytesReference bytes = XContentHelper.childBytes(parser);
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||||
|
assertNull(parser.nextToken());
|
||||||
|
|
||||||
|
assertEquals("{}", bytes.utf8ToString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue