Skip to content

Commit e241dc0

Browse files
felixbarnyelasticsearchmachine
authored andcommitted
Json processor: allow duplicate keys (elastic#74956)
1 parent 8dab892 commit e241dc0

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
@@ -32,6 +32,11 @@ public XContentType contentType() {
3232
return in.contentType();
3333
}
3434

35+
@Override
36+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
37+
in.allowDuplicateKeys(allowDuplicateKeys);
38+
}
39+
3540
@Override
3641
public Token nextToken() throws IOException {
3742
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
@@ -111,6 +111,8 @@ enum NumberType {
111111

112112
XContentType contentType();
113113

114+
void allowDuplicateKeys(boolean allowDuplicateKeys);
115+
114116
Token nextToken() throws IOException;
115117

116118
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
@@ -42,6 +42,11 @@ public XContentType contentType() {
4242
return parser.contentType();
4343
}
4444

45+
@Override
46+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
47+
parser.allowDuplicateKeys(allowDuplicateKeys);
48+
}
49+
4550
@Override
4651
public Token nextToken() throws IOException {
4752
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
@@ -25,4 +25,9 @@ public CborXContentParser(NamedXContentRegistry xContentRegistry,
2525
public XContentType contentType() {
2626
return XContentType.CBOR;
2727
}
28+
29+
@Override
30+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
31+
throw new UnsupportedOperationException("Allowing duplicate keys after the parser has been created is not possible for CBOR");
32+
}
2833
}

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
@@ -36,6 +36,11 @@ public XContentType contentType() {
3636
return XContentType.JSON;
3737
}
3838

39+
@Override
40+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
41+
parser.configure(JsonParser.Feature.STRICT_DUPLICATE_DETECTION, allowDuplicateKeys == false);
42+
}
43+
3944
@Override
4045
public Token nextToken() throws IOException {
4146
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
@@ -25,4 +25,9 @@ public SmileXContentParser(NamedXContentRegistry xContentRegistry,
2525
public XContentType contentType() {
2626
return XContentType.SMILE;
2727
}
28+
29+
@Override
30+
public void allowDuplicateKeys(boolean allowDuplicateKeys) {
31+
throw new UnsupportedOperationException("Allowing duplicate keys after the parser has been created is not possible for Smile");
32+
}
2833
}

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)