Skip to content

Commit 67fbc33

Browse files
authored
Json processor: allow duplicate keys (#74956)
1 parent 265f783 commit 67fbc33

File tree

15 files changed

+150
-27
lines changed

15 files changed

+150
-27
lines changed

docs/reference/ingest/processors/json.asciidoc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ Converts a JSON string into a structured JSON object.
1010
.Json Options
1111
[options="header"]
1212
|======
13-
| Name | Required | Default | Description
14-
| `field` | yes | - | The field to be parsed.
15-
| `target_field` | no | `field` | The field that the converted structured object will be written into. Any existing content in this field will be overwritten.
16-
| `add_to_root` | no | false | Flag that forces the serialized json to be injected into the top level of the document. `target_field` must not be set when this option is chosen.
13+
| Name | Required | Default | Description
14+
| `field` | yes | - | The field to be parsed.
15+
| `target_field` | no | `field` | The field that the converted structured object will be written into. Any existing content in this field will be overwritten.
16+
| `add_to_root` | no | false | Flag that forces the serialized json to be injected into the top level of the document. `target_field` must not be set when this option is chosen.
17+
| `allow_duplicate_keys` | no | false | When set to `true`, the JSON parser will not fail if the JSON contains duplicate keys.
18+
Instead, the latest value wins. Allowing duplicate keys also improves execution time.
1719
include::common-options.asciidoc[]
1820
|======
1921

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/FilterXContentParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public XContentType contentType() {
3333
return in.contentType();
3434
}
3535

36+
@Override
37+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
38+
in.allowDuplicateKeys(allowDuplicateKeys);
39+
}
40+
3641
@Override
3742
public Token nextToken() throws IOException {
3843
return in.nextToken();

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentParser.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ enum NumberType {
112112

113113
XContentType contentType();
114114

115+
void allowDuplicateKeys(boolean allowDuplicateKeys);
116+
115117
Token nextToken() throws IOException;
116118

117119
void skipChildren() throws IOException;

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentSubParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ public XContentType contentType() {
4343
return parser.contentType();
4444
}
4545

46+
@Override
47+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
48+
parser.allowDuplicateKeys(allowDuplicateKeys);
49+
}
50+
4651
@Override
4752
public Token nextToken() throws IOException {
4853
if (level > 0) {

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/cbor/CborXContentParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,9 @@ public CborXContentParser(NamedXContentRegistry xContentRegistry,
3232
public XContentType contentType() {
3333
return XContentType.CBOR;
3434
}
35+
36+
@Override
37+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
38+
throw new UnsupportedOperationException("Allowing duplicate keys after the parser has been created is not possible for CBOR");
39+
}
3540
}

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/json/JsonXContentParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public XContentType contentType() {
4444
return XContentType.JSON;
4545
}
4646

47+
@Override
48+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
49+
parser.configure(JsonParser.Feature.STRICT_DUPLICATE_DETECTION, allowDuplicateKeys == false);
50+
}
51+
4752
@Override
4853
public Token nextToken() throws IOException {
4954
return convertToken(parser.nextToken());

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/smile/SmileXContentParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,9 @@ public SmileXContentParser(NamedXContentRegistry xContentRegistry,
3232
public XContentType contentType() {
3333
return XContentType.SMILE;
3434
}
35+
36+
@Override
37+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
38+
throw new UnsupportedOperationException("Allowing duplicate keys after the parser has been created is not possible for Smile");
39+
}
3540
}

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/support/MapXContentParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ public XContentType contentType() {
9090
return xContentType;
9191
}
9292

93+
@Override
94+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
95+
throw new UnsupportedOperationException("Allowing duplicate keys is not possible for maps");
96+
}
97+
9398
@Override
9499
public Token nextToken() throws IOException {
95100
if (iterator == null) {

modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/JsonProcessor.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ public final class JsonProcessor extends AbstractProcessor {
3636
private final String field;
3737
private final String targetField;
3838
private final boolean addToRoot;
39+
private final boolean allowDuplicateKeys;
3940

40-
JsonProcessor(String tag, String description, String field, String targetField, boolean addToRoot) {
41+
JsonProcessor(String tag, String description, String field, String targetField, boolean addToRoot, boolean allowDuplicateKeys) {
4142
super(tag, description);
4243
this.field = field;
4344
this.targetField = targetField;
4445
this.addToRoot = addToRoot;
46+
this.allowDuplicateKeys = allowDuplicateKeys;
4547
}
4648

4749
public String getField() {
@@ -56,11 +58,12 @@ boolean isAddToRoot() {
5658
return addToRoot;
5759
}
5860

59-
public static Object apply(Object fieldValue) {
61+
public static Object apply(Object fieldValue, boolean allowDuplicateKeys) {
6062
BytesReference bytesRef = fieldValue == null ? new BytesArray("null") : new BytesArray(fieldValue.toString());
6163
try (InputStream stream = bytesRef.streamInput();
6264
XContentParser parser = JsonXContent.jsonXContent
6365
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, stream)) {
66+
parser.allowDuplicateKeys(allowDuplicateKeys);
6467
XContentParser.Token token = parser.nextToken();
6568
Object value = null;
6669
if (token == XContentParser.Token.VALUE_NULL) {
@@ -84,12 +87,12 @@ public static Object apply(Object fieldValue) {
8487
}
8588
}
8689

87-
public static void apply(Map<String, Object> ctx, String fieldName) {
88-
Object value = apply(ctx.get(fieldName));
90+
public static void apply(Map<String, Object> ctx, String fieldName, boolean allowDuplicateKeys) {
91+
Object value = apply(ctx.get(fieldName), allowDuplicateKeys);
8992
if (value instanceof Map) {
9093
@SuppressWarnings("unchecked")
9194
Map<String, Object> map = (Map<String, Object>) value;
92-
ctx.putAll(map);
95+
ctx.putAll(map);
9396
} else {
9497
throw new IllegalArgumentException("cannot add non-map fields to root of document");
9598
}
@@ -98,9 +101,9 @@ public static void apply(Map<String, Object> ctx, String fieldName) {
98101
@Override
99102
public IngestDocument execute(IngestDocument document) throws Exception {
100103
if (addToRoot) {
101-
apply(document.getSourceAndMetadata(), field);
104+
apply(document.getSourceAndMetadata(), field, allowDuplicateKeys);
102105
} else {
103-
document.setFieldValue(targetField, apply(document.getFieldValue(field, Object.class)));
106+
document.setFieldValue(targetField, apply(document.getFieldValue(field, Object.class), allowDuplicateKeys));
104107
}
105108
return document;
106109
}
@@ -117,6 +120,7 @@ public JsonProcessor create(Map<String, Processor.Factory> registry, String proc
117120
String field = ConfigurationUtils.readStringProperty(TYPE, processorTag, config, "field");
118121
String targetField = ConfigurationUtils.readOptionalStringProperty(TYPE, processorTag, config, "target_field");
119122
boolean addToRoot = ConfigurationUtils.readBooleanProperty(TYPE, processorTag, config, "add_to_root", false);
123+
boolean allowDuplicateKeys = ConfigurationUtils.readBooleanProperty(TYPE, processorTag, config, "allow_duplicate_keys", false);
120124

121125
if (addToRoot && targetField != null) {
122126
throw newConfigurationException(TYPE, processorTag, "target_field",
@@ -127,7 +131,7 @@ public JsonProcessor create(Map<String, Processor.Factory> registry, String proc
127131
targetField = field;
128132
}
129133

130-
return new JsonProcessor(processorTag, description, field, targetField, addToRoot);
134+
return new JsonProcessor(processorTag, description, field, targetField, addToRoot, allowDuplicateKeys);
131135
}
132136
}
133137
}

modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/Processors.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static String uppercase(String value) {
5959
* @return structured JSON object
6060
*/
6161
public static Object json(Object fieldValue) {
62-
return JsonProcessor.apply(fieldValue);
62+
return JsonProcessor.apply(fieldValue, false);
6363
}
6464

6565
/**
@@ -72,7 +72,7 @@ public static Object json(Object fieldValue) {
7272
* contains the JSON string
7373
*/
7474
public static void json(Map<String, Object> map, String field) {
75-
JsonProcessor.apply(map, field);
75+
JsonProcessor.apply(map, field, false);
7676
}
7777

7878
/**

0 commit comments

Comments
 (0)