[7.x] Implement XContentParser.genericMap and XContentParser.genericMapOrdered methods (#42059) (#43575)
This commit is contained in:
parent
b15e40ffad
commit
c702cd7415
|
@ -19,11 +19,14 @@
|
|||
|
||||
package org.elasticsearch.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.CheckedFunction;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Interface for pull - parsing {@link XContent} see {@link XContentType} for supported types.
|
||||
|
@ -135,6 +138,18 @@ public interface XContentParser extends Closeable {
|
|||
|
||||
Map<String, String> mapStringsOrdered() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link Map} holding parsed map.
|
||||
* Serves as a replacement for the "map", "mapOrdered", "mapStrings" and "mapStringsOrdered" methods above.
|
||||
*
|
||||
* @param mapFactory factory for creating new {@link Map} objects
|
||||
* @param mapValueParser parser for parsing a single map value
|
||||
* @param <T> map value type
|
||||
* @return {@link Map} object
|
||||
*/
|
||||
<T> Map<String, T> map(
|
||||
Supplier<Map<String, T>> mapFactory, CheckedFunction<XContentParser, T, IOException> mapValueParser) throws IOException;
|
||||
|
||||
List<Object> list() throws IOException;
|
||||
|
||||
List<Object> listOrderedMap() throws IOException;
|
||||
|
|
|
@ -19,10 +19,13 @@
|
|||
|
||||
package org.elasticsearch.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.CheckedFunction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Wrapper for a XContentParser that makes a single object/array look like a complete document.
|
||||
|
@ -110,6 +113,12 @@ public class XContentSubParser implements XContentParser {
|
|||
return parser.mapStringsOrdered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Map<String, T> map(
|
||||
Supplier<Map<String, T>> mapFactory, CheckedFunction<XContentParser, T, IOException> mapValueParser) throws IOException {
|
||||
return parser.map(mapFactory, mapValueParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> list() throws IOException {
|
||||
return parser.list();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.common.xcontent.support;
|
||||
|
||||
import org.elasticsearch.common.Booleans;
|
||||
import org.elasticsearch.common.CheckedFunction;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.XContentParseException;
|
||||
|
@ -34,6 +35,7 @@ import java.util.HashMap;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public abstract class AbstractXContentParser implements XContentParser {
|
||||
|
||||
|
@ -279,6 +281,12 @@ public abstract class AbstractXContentParser implements XContentParser {
|
|||
return readOrderedMapStrings(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Map<String, T> map(
|
||||
Supplier<Map<String, T>> mapFactory, CheckedFunction<XContentParser, T, IOException> mapValueParser) throws IOException {
|
||||
return readGenericMap(this, mapFactory, mapValueParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> list() throws IOException {
|
||||
return readList(this);
|
||||
|
@ -289,21 +297,13 @@ public abstract class AbstractXContentParser implements XContentParser {
|
|||
return readListOrderedMap(this);
|
||||
}
|
||||
|
||||
public interface MapFactory {
|
||||
Map<String, Object> newMap();
|
||||
}
|
||||
static final Supplier<Map<String, Object>> SIMPLE_MAP_FACTORY = HashMap::new;
|
||||
|
||||
interface MapStringsFactory {
|
||||
Map<String, String> newMap();
|
||||
}
|
||||
static final Supplier<Map<String, Object>> ORDERED_MAP_FACTORY = LinkedHashMap::new;
|
||||
|
||||
static final MapFactory SIMPLE_MAP_FACTORY = HashMap::new;
|
||||
static final Supplier<Map<String, String>> SIMPLE_MAP_STRINGS_FACTORY = HashMap::new;
|
||||
|
||||
static final MapFactory ORDERED_MAP_FACTORY = LinkedHashMap::new;
|
||||
|
||||
static final MapStringsFactory SIMPLE_MAP_STRINGS_FACTORY = HashMap::new;
|
||||
|
||||
static final MapStringsFactory ORDERED_MAP_STRINGS_FACTORY = LinkedHashMap::new;
|
||||
static final Supplier<Map<String, String>> ORDERED_MAP_STRINGS_FACTORY = LinkedHashMap::new;
|
||||
|
||||
static Map<String, Object> readMap(XContentParser parser) throws IOException {
|
||||
return readMap(parser, SIMPLE_MAP_FACTORY);
|
||||
|
@ -329,28 +329,19 @@ public abstract class AbstractXContentParser implements XContentParser {
|
|||
return readList(parser, ORDERED_MAP_FACTORY);
|
||||
}
|
||||
|
||||
static Map<String, Object> readMap(XContentParser parser, MapFactory mapFactory) throws IOException {
|
||||
Map<String, Object> map = mapFactory.newMap();
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
}
|
||||
if (token == XContentParser.Token.START_OBJECT) {
|
||||
token = parser.nextToken();
|
||||
}
|
||||
for (; token == XContentParser.Token.FIELD_NAME; token = parser.nextToken()) {
|
||||
// Must point to field name
|
||||
String fieldName = parser.currentName();
|
||||
// And then the value...
|
||||
token = parser.nextToken();
|
||||
Object value = readValue(parser, mapFactory, token);
|
||||
map.put(fieldName, value);
|
||||
}
|
||||
return map;
|
||||
static Map<String, Object> readMap(XContentParser parser, Supplier<Map<String, Object>> mapFactory) throws IOException {
|
||||
return readGenericMap(parser, mapFactory, p -> readValue(p, mapFactory));
|
||||
}
|
||||
|
||||
static Map<String, String> readMapStrings(XContentParser parser, MapStringsFactory mapStringsFactory) throws IOException {
|
||||
Map<String, String> map = mapStringsFactory.newMap();
|
||||
static Map<String, String> readMapStrings(XContentParser parser, Supplier<Map<String, String>> mapFactory) throws IOException {
|
||||
return readGenericMap(parser, mapFactory, XContentParser::text);
|
||||
}
|
||||
|
||||
static <T> Map<String, T> readGenericMap(
|
||||
XContentParser parser,
|
||||
Supplier<Map<String, T>> mapFactory,
|
||||
CheckedFunction<XContentParser, T, IOException> mapValueParser) throws IOException {
|
||||
Map<String, T> map = mapFactory.get();
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
|
@ -363,13 +354,13 @@ public abstract class AbstractXContentParser implements XContentParser {
|
|||
String fieldName = parser.currentName();
|
||||
// And then the value...
|
||||
parser.nextToken();
|
||||
String value = parser.text();
|
||||
T value = mapValueParser.apply(parser);
|
||||
map.put(fieldName, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
static List<Object> readList(XContentParser parser, MapFactory mapFactory) throws IOException {
|
||||
static List<Object> readList(XContentParser parser, Supplier<Map<String, Object>> mapFactory) throws IOException {
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
|
@ -386,28 +377,22 @@ public abstract class AbstractXContentParser implements XContentParser {
|
|||
|
||||
ArrayList<Object> list = new ArrayList<>();
|
||||
for (; token != null && token != XContentParser.Token.END_ARRAY; token = parser.nextToken()) {
|
||||
list.add(readValue(parser, mapFactory, token));
|
||||
list.add(readValue(parser, mapFactory));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static Object readValue(XContentParser parser, MapFactory mapFactory, XContentParser.Token token) throws IOException {
|
||||
if (token == XContentParser.Token.VALUE_NULL) {
|
||||
return null;
|
||||
} else if (token == XContentParser.Token.VALUE_STRING) {
|
||||
return parser.text();
|
||||
} else if (token == XContentParser.Token.VALUE_NUMBER) {
|
||||
return parser.numberValue();
|
||||
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
|
||||
return parser.booleanValue();
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
return readMap(parser, mapFactory);
|
||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||
return readList(parser, mapFactory);
|
||||
} else if (token == XContentParser.Token.VALUE_EMBEDDED_OBJECT) {
|
||||
return parser.binaryValue();
|
||||
public static Object readValue(XContentParser parser, Supplier<Map<String, Object>> mapFactory) throws IOException {
|
||||
switch (parser.currentToken()) {
|
||||
case VALUE_STRING: return parser.text();
|
||||
case VALUE_NUMBER: return parser.numberValue();
|
||||
case VALUE_BOOLEAN: return parser.booleanValue();
|
||||
case START_OBJECT: return readMap(parser, mapFactory);
|
||||
case START_ARRAY: return readList(parser, mapFactory);
|
||||
case VALUE_EMBEDDED_OBJECT: return parser.binaryValue();
|
||||
case VALUE_NULL:
|
||||
default: return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
|
||||
/**
|
||||
* Simple structure with 3 fields: int, double and String.
|
||||
* Used for testing parsers.
|
||||
*/
|
||||
class SimpleStruct implements ToXContentObject {
|
||||
|
||||
static SimpleStruct fromXContent(XContentParser parser) {
|
||||
return PARSER.apply(parser, null);
|
||||
}
|
||||
|
||||
private static final ParseField I = new ParseField("i");
|
||||
private static final ParseField D = new ParseField("d");
|
||||
private static final ParseField S = new ParseField("s");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final ConstructingObjectParser<SimpleStruct, Void> PARSER =
|
||||
new ConstructingObjectParser<>(
|
||||
"simple_struct", true, args -> new SimpleStruct((int) args[0], (double) args[1], (String) args[2]));
|
||||
|
||||
static {
|
||||
PARSER.declareInt(constructorArg(), I);
|
||||
PARSER.declareDouble(constructorArg(), D);
|
||||
PARSER.declareString(constructorArg(), S);
|
||||
}
|
||||
|
||||
private final int i;
|
||||
private final double d;
|
||||
private final String s;
|
||||
|
||||
SimpleStruct(int i, double d, String s) {
|
||||
this.i = i;
|
||||
this.d = d;
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder
|
||||
.startObject()
|
||||
.field(I.getPreferredName(), i)
|
||||
.field(D.getPreferredName(), d)
|
||||
.field(S.getPreferredName(), s)
|
||||
.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SimpleStruct other = (SimpleStruct) o;
|
||||
return i == other.i && d == other.d && Objects.equals(s, other.s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(i, d, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Strings.toString(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,18 +30,21 @@ import java.io.IOException;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.isIn;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
|
||||
|
||||
public class XContentParserTests extends ESTestCase {
|
||||
|
||||
|
@ -329,6 +332,65 @@ public class XContentParserTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testGenericMap() throws IOException {
|
||||
String content = "{" +
|
||||
"\"c\": { \"i\": 3, \"d\": 0.3, \"s\": \"ccc\" }, " +
|
||||
"\"a\": { \"i\": 1, \"d\": 0.1, \"s\": \"aaa\" }, " +
|
||||
"\"b\": { \"i\": 2, \"d\": 0.2, \"s\": \"bbb\" }" +
|
||||
"}";
|
||||
SimpleStruct structA = new SimpleStruct(1, 0.1, "aaa");
|
||||
SimpleStruct structB = new SimpleStruct(2, 0.2, "bbb");
|
||||
SimpleStruct structC = new SimpleStruct(3, 0.3, "ccc");
|
||||
Map<String, SimpleStruct> expectedMap = new HashMap<>();
|
||||
expectedMap.put("a", structA);
|
||||
expectedMap.put("b", structB);
|
||||
expectedMap.put("c", structC);
|
||||
try (XContentParser parser = createParser(JsonXContent.jsonXContent, content)) {
|
||||
Map<String, SimpleStruct> actualMap = parser.map(HashMap::new, SimpleStruct::fromXContent);
|
||||
// Verify map contents, ignore the iteration order.
|
||||
assertThat(actualMap, equalTo(expectedMap));
|
||||
assertThat(actualMap.values(), containsInAnyOrder(structA, structB, structC));
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
}
|
||||
|
||||
public void testGenericMapOrdered() throws IOException {
|
||||
String content = "{" +
|
||||
"\"c\": { \"i\": 3, \"d\": 0.3, \"s\": \"ccc\" }, " +
|
||||
"\"a\": { \"i\": 1, \"d\": 0.1, \"s\": \"aaa\" }, " +
|
||||
"\"b\": { \"i\": 2, \"d\": 0.2, \"s\": \"bbb\" }" +
|
||||
"}";
|
||||
SimpleStruct structA = new SimpleStruct(1, 0.1, "aaa");
|
||||
SimpleStruct structB = new SimpleStruct(2, 0.2, "bbb");
|
||||
SimpleStruct structC = new SimpleStruct(3, 0.3, "ccc");
|
||||
Map<String, SimpleStruct> expectedMap = new HashMap<>();
|
||||
expectedMap.put("a", structA);
|
||||
expectedMap.put("b", structB);
|
||||
expectedMap.put("c", structC);
|
||||
try (XContentParser parser = createParser(JsonXContent.jsonXContent, content)) {
|
||||
Map<String, SimpleStruct> actualMap = parser.map(LinkedHashMap::new, SimpleStruct::fromXContent);
|
||||
// Verify map contents, ignore the iteration order.
|
||||
assertThat(actualMap, equalTo(expectedMap));
|
||||
// Verify that map's iteration order is the same as the order in which fields appear in JSON.
|
||||
assertThat(actualMap.values(), contains(structC, structA, structB));
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
}
|
||||
|
||||
public void testGenericMap_Failure_MapContainingUnparsableValue() throws IOException {
|
||||
String content = "{" +
|
||||
"\"a\": { \"i\": 1, \"d\": 0.1, \"s\": \"aaa\" }, " +
|
||||
"\"b\": { \"i\": 2, \"d\": 0.2, \"s\": 666 }, " +
|
||||
"\"c\": { \"i\": 3, \"d\": 0.3, \"s\": \"ccc\" }" +
|
||||
"}";
|
||||
try (XContentParser parser = createParser(JsonXContent.jsonXContent, content)) {
|
||||
XContentParseException exception = expectThrows(
|
||||
XContentParseException.class,
|
||||
() -> parser.map(HashMap::new, SimpleStruct::fromXContent));
|
||||
assertThat(exception, hasMessage(containsString("s doesn't support values of type: VALUE_NUMBER")));
|
||||
}
|
||||
}
|
||||
|
||||
public void testSubParserObject() throws IOException {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
int numberOfTokens;
|
||||
|
|
|
@ -282,7 +282,7 @@ public abstract class FieldMapper extends Mapper implements Cloneable {
|
|||
String valuePreview = "";
|
||||
try {
|
||||
XContentParser parser = context.parser();
|
||||
Object complexValue = AbstractXContentParser.readValue(parser, HashMap::new, parser.currentToken());
|
||||
Object complexValue = AbstractXContentParser.readValue(parser, HashMap::new);
|
||||
if (complexValue == null) {
|
||||
valuePreview = "null";
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.xpack.core.watcher.support.xcontent;
|
||||
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.CheckedFunction;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
|
@ -21,6 +22,7 @@ import java.time.Clock;
|
|||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A xcontent parser that is used by watcher. This is a special parser that is
|
||||
|
@ -123,6 +125,12 @@ public class WatcherXContentParser implements XContentParser {
|
|||
return parser.mapStringsOrdered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Map<String, T> map(
|
||||
Supplier<Map<String, T>> mapFactory, CheckedFunction<XContentParser, T, IOException> mapValueParser) throws IOException {
|
||||
return parser.map(mapFactory, mapValueParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> list() throws IOException {
|
||||
return parser.list();
|
||||
|
|
Loading…
Reference in New Issue