Skip to content

Commit 2d3e331

Browse files
authored
Improve resiliency to formatting JSON in server (#48553)
Make a number of changes so that JSON in the server directory is more resilient to automatic formatting. This covers: * Reformatting multiline JSON to embed whitespace in the strings * Add helper method `stripWhitespace()`, to strip whitespace from a JSON document using XContent methods. This is sometimes necessary where a test is comparing some machine-generated JSON with an expected value.
1 parent 1b3c1d2 commit 2d3e331

File tree

7 files changed

+199
-124
lines changed

7 files changed

+199
-124
lines changed

server/src/main/java/org/elasticsearch/common/xcontent/XContentHelper.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.lucene.util.BytesRef;
2323
import org.elasticsearch.ElasticsearchParseException;
2424
import org.elasticsearch.common.Strings;
25+
import org.elasticsearch.common.bytes.BytesArray;
2526
import org.elasticsearch.common.bytes.BytesReference;
2627
import org.elasticsearch.common.collect.Tuple;
2728
import org.elasticsearch.common.compress.Compressor;
@@ -161,6 +162,19 @@ public static String convertToJson(BytesReference bytes, boolean reformatJson, X
161162
return convertToJson(bytes, reformatJson, false, xContentType);
162163
}
163164

165+
/**
166+
* Accepts a JSON string, parses it and prints it without pretty-printing it. This is useful
167+
* where a piece of JSON is formatted for legibility, but needs to be stripped of unnecessary
168+
* whitespace e.g. for comparison in a test.
169+
*
170+
* @param json the JSON to format
171+
* @return reformatted JSON
172+
* @throws IOException if the reformatting fails, e.g. because the JSON is not well-formed
173+
*/
174+
public static String stripWhitespace(String json) throws IOException {
175+
return convertToJson(new BytesArray(json), true, XContentType.JSON);
176+
}
177+
164178
public static String convertToJson(BytesReference bytes, boolean reformatJson, boolean prettyPrint, XContentType xContentType)
165179
throws IOException {
166180
Objects.requireNonNull(xContentType);

server/src/test/java/org/elasticsearch/action/search/SearchPhaseExecutionExceptionTests.java

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
2929
import org.elasticsearch.common.xcontent.ToXContent;
3030
import org.elasticsearch.common.xcontent.XContent;
31+
import org.elasticsearch.common.xcontent.XContentHelper;
3132
import org.elasticsearch.common.xcontent.XContentParser;
3233
import org.elasticsearch.common.xcontent.XContentType;
3334
import org.elasticsearch.index.shard.IndexShardClosedException;
@@ -56,36 +57,40 @@ public void testToXContent() throws IOException {
5657
});
5758

5859
// Failures are grouped (by default)
59-
assertEquals("{" +
60-
"\"type\":\"search_phase_execution_exception\"," +
61-
"\"reason\":\"all shards failed\"," +
62-
"\"phase\":\"test\"," +
63-
"\"grouped\":true," +
64-
"\"failed_shards\":[" +
65-
"{" +
66-
"\"shard\":0," +
67-
"\"index\":\"foo\"," +
68-
"\"node\":\"node_1\"," +
69-
"\"reason\":{" +
70-
"\"type\":\"parsing_exception\"," +
71-
"\"reason\":\"foobar\"," +
72-
"\"line\":1," +
73-
"\"col\":2" +
74-
"}" +
75-
"}," +
76-
"{" +
77-
"\"shard\":1," +
78-
"\"index\":\"foo\"," +
79-
"\"node\":\"node_2\"," +
80-
"\"reason\":{" +
81-
"\"type\":\"index_shard_closed_exception\"," +
82-
"\"reason\":\"CurrentState[CLOSED] Closed\"," +
83-
"\"index_uuid\":\"_na_\"," +
84-
"\"shard\":\"1\"," +
85-
"\"index\":\"foo\"" +
86-
"}" +
87-
"}" +
88-
"]}", Strings.toString(exception));
60+
final String expectedJson = XContentHelper.stripWhitespace(
61+
"{"
62+
+ " \"type\": \"search_phase_execution_exception\","
63+
+ " \"reason\": \"all shards failed\","
64+
+ " \"phase\": \"test\","
65+
+ " \"grouped\": true,"
66+
+ " \"failed_shards\": ["
67+
+ " {"
68+
+ " \"shard\": 0,"
69+
+ " \"index\": \"foo\","
70+
+ " \"node\": \"node_1\","
71+
+ " \"reason\": {"
72+
+ " \"type\": \"parsing_exception\","
73+
+ " \"reason\": \"foobar\","
74+
+ " \"line\": 1,"
75+
+ " \"col\": 2"
76+
+ " }"
77+
+ " },"
78+
+ " {"
79+
+ " \"shard\": 1,"
80+
+ " \"index\": \"foo\","
81+
+ " \"node\": \"node_2\","
82+
+ " \"reason\": {"
83+
+ " \"type\": \"index_shard_closed_exception\","
84+
+ " \"reason\": \"CurrentState[CLOSED] Closed\","
85+
+ " \"index_uuid\": \"_na_\","
86+
+ " \"shard\": \"1\","
87+
+ " \"index\": \"foo\""
88+
+ " }"
89+
+ " }"
90+
+ " ]"
91+
+ "}"
92+
);
93+
assertEquals(expectedJson, Strings.toString(exception));
8994
}
9095

9196
public void testToAndFromXContent() throws IOException {

server/src/test/java/org/elasticsearch/search/sort/ScriptSortBuilderTests.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,18 +165,19 @@ public void testScriptSortTypeIllegalArgument() {
165165
}
166166

167167
public void testParseJson() throws IOException {
168-
String scriptSort = "{\n" +
169-
"\"_script\" : {\n" +
170-
"\"type\" : \"number\",\n" +
171-
"\"script\" : {\n" +
172-
"\"source\": \"doc['field_name'].value * factor\",\n" +
173-
"\"params\" : {\n" +
174-
"\"factor\" : 1.1\n" +
175-
"}\n" +
176-
"},\n" +
177-
"\"mode\" : \"max\",\n" +
178-
"\"order\" : \"asc\"\n" +
179-
"} }\n";
168+
String scriptSort = "{"
169+
+ " \"_script\": {"
170+
+ " \"type\": \"number\","
171+
+ " \"script\": {"
172+
+ " \"source\": \"doc['field_name'].value * factor\","
173+
+ " \"params\": {"
174+
+ " \"factor\": 1.1"
175+
+ " }"
176+
+ " },"
177+
+ " \"mode\": \"max\","
178+
+ " \"order\": \"asc\""
179+
+ " }"
180+
+ "}";
180181
try (XContentParser parser = createParser(JsonXContent.jsonXContent, scriptSort)) {
181182
parser.nextToken();
182183
parser.nextToken();

server/src/test/java/org/elasticsearch/search/suggest/SuggestTests.java

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import java.util.List;
5353

5454
import static java.util.Collections.emptyList;
55+
import static org.elasticsearch.common.xcontent.XContentHelper.stripWhitespace;
5556
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
5657
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
5758
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
@@ -131,19 +132,28 @@ public void testToXContent() throws IOException {
131132
Suggest suggest = new Suggest(Collections.singletonList(suggestion));
132133
BytesReference xContent = toXContent(suggest, XContentType.JSON, randomBoolean());
133134
assertEquals(
134-
"{\"suggest\":"
135-
+ "{\"suggestionName\":"
136-
+ "[{\"text\":\"entryText\","
137-
+ "\"offset\":42,"
138-
+ "\"length\":313,"
139-
+ "\"options\":[{\"text\":\"someText\","
140-
+ "\"highlighted\":\"somethingHighlighted\","
141-
+ "\"score\":1.3,"
142-
+ "\"collate_match\":true}]"
143-
+ "}]"
144-
+ "}"
145-
+"}",
146-
xContent.utf8ToString());
135+
stripWhitespace(
136+
"{"
137+
+ " \"suggest\": {"
138+
+ " \"suggestionName\": ["
139+
+ " {"
140+
+ " \"text\": \"entryText\","
141+
+ " \"offset\": 42,"
142+
+ " \"length\": 313,"
143+
+ " \"options\": ["
144+
+ " {"
145+
+ " \"text\": \"someText\","
146+
+ " \"highlighted\": \"somethingHighlighted\","
147+
+ " \"score\": 1.3,"
148+
+ " \"collate_match\": true"
149+
+ " }"
150+
+ " ]"
151+
+ " }"
152+
+ " ]"
153+
+ " }"
154+
+ "}"
155+
),
156+
xContent.utf8ToString());
147157
}
148158

149159
public void testFilter() throws Exception {

server/src/test/java/org/elasticsearch/search/suggest/SuggestionOptionTests.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,14 @@ private void doTestFromXContent(boolean addRandomFields) throws IOException {
8181
public void testToXContent() throws IOException {
8282
Option option = new PhraseSuggestion.Entry.Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
8383
BytesReference xContent = toXContent(option, XContentType.JSON, randomBoolean());
84-
assertEquals("{\"text\":\"someText\","
85-
+ "\"highlighted\":\"somethingHighlighted\","
86-
+ "\"score\":1.3,"
87-
+ "\"collate_match\":true"
88-
+ "}"
89-
, xContent.utf8ToString());
84+
assertEquals(
85+
("{"
86+
+ " \"text\": \"someText\","
87+
+ " \"highlighted\": \"somethingHighlighted\","
88+
+ " \"score\": 1.3,"
89+
+ " \"collate_match\": true"
90+
+ "}").replaceAll("\\s+", ""),
91+
xContent.utf8ToString()
92+
);
9093
}
9194
}

server/src/test/java/org/elasticsearch/search/suggest/SuggestionTests.java

Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -164,17 +164,23 @@ public void testFromXContentWithoutTypeParam() throws IOException {
164164

165165
public void testUnknownSuggestionTypeThrows() throws IOException {
166166
XContent xContent = JsonXContent.jsonXContent;
167-
String suggestionString =
168-
"{\"unknownType#suggestionName\":"
169-
+ "[{\"text\":\"entryText\","
170-
+ "\"offset\":42,"
171-
+ "\"length\":313,"
172-
+ "\"options\":[{\"text\":\"someText\","
173-
+ "\"highlighted\":\"somethingHighlighted\","
174-
+ "\"score\":1.3,"
175-
+ "\"collate_match\":true}]"
176-
+ "}]"
177-
+ "}";
167+
String suggestionString = ("{"
168+
+ " \"unknownType#suggestionName\": ["
169+
+ " {"
170+
+ " \"text\": \"entryText\","
171+
+ " \"offset\": 42,"
172+
+ " \"length\": 313,"
173+
+ " \"options\": ["
174+
+ " {"
175+
+ " \"text\": \"someText\","
176+
+ " \"highlighted\": \"somethingHighlighted\","
177+
+ " \"score\": 1.3,"
178+
+ " \"collate_match\": true"
179+
+ " }"
180+
+ " ]"
181+
+ " }"
182+
+ " ]"
183+
+ "}").replaceAll("\\s+", "");
178184
try (XContentParser parser = xContent.createParser(xContentRegistry(),
179185
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, suggestionString)) {
180186
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
@@ -195,18 +201,25 @@ public void testToXContent() throws IOException {
195201
PhraseSuggestion suggestion = new PhraseSuggestion("suggestionName", 5);
196202
suggestion.addTerm(entry);
197203
BytesReference xContent = toXContent(suggestion, XContentType.JSON, params, randomBoolean());
198-
assertEquals(
199-
"{\"phrase#suggestionName\":[{"
200-
+ "\"text\":\"entryText\","
201-
+ "\"offset\":42,"
202-
+ "\"length\":313,"
203-
+ "\"options\":[{"
204-
+ "\"text\":\"someText\","
205-
+ "\"highlighted\":\"somethingHighlighted\","
206-
+ "\"score\":1.3,"
207-
+ "\"collate_match\":true}]"
208-
+ "}]"
209-
+ "}", xContent.utf8ToString());
204+
assertEquals(("{"
205+
+ " \"phrase#suggestionName\": ["
206+
+ " {"
207+
+ " \"text\": \"entryText\","
208+
+ " \"offset\": 42,"
209+
+ " \"length\": 313,"
210+
+ " \"options\": ["
211+
+ " {"
212+
+ " \"text\": \"someText\","
213+
+ " \"highlighted\": \"somethingHighlighted\","
214+
+ " \"score\": 1.3,"
215+
+ " \"collate_match\": true"
216+
+ " }"
217+
+ " ]"
218+
+ " }"
219+
+ " ]"
220+
+ "}").replaceAll("\\s+", ""),
221+
xContent.utf8ToString()
222+
);
210223
}
211224
{
212225
PhraseSuggestion.Entry.Option option = new PhraseSuggestion.Entry.Option(new Text("someText"), new Text("somethingHighlighted"),
@@ -216,18 +229,25 @@ public void testToXContent() throws IOException {
216229
PhraseSuggestion suggestion = new PhraseSuggestion("suggestionName", 5);
217230
suggestion.addTerm(entry);
218231
BytesReference xContent = toXContent(suggestion, XContentType.JSON, params, randomBoolean());
219-
assertEquals(
220-
"{\"phrase#suggestionName\":[{"
221-
+ "\"text\":\"entryText\","
222-
+ "\"offset\":42,"
223-
+ "\"length\":313,"
224-
+ "\"options\":[{"
225-
+ "\"text\":\"someText\","
226-
+ "\"highlighted\":\"somethingHighlighted\","
227-
+ "\"score\":1.3,"
228-
+ "\"collate_match\":true}]"
229-
+ "}]"
230-
+ "}", xContent.utf8ToString());
232+
assertEquals(("{"
233+
+ " \"phrase#suggestionName\": ["
234+
+ " {"
235+
+ " \"text\": \"entryText\","
236+
+ " \"offset\": 42,"
237+
+ " \"length\": 313,"
238+
+ " \"options\": ["
239+
+ " {"
240+
+ " \"text\": \"someText\","
241+
+ " \"highlighted\": \"somethingHighlighted\","
242+
+ " \"score\": 1.3,"
243+
+ " \"collate_match\": true"
244+
+ " }"
245+
+ " ]"
246+
+ " }"
247+
+ " ]"
248+
+ "}").replaceAll("\\s+", ""),
249+
xContent.utf8ToString()
250+
);
231251
}
232252
{
233253
TermSuggestion.Entry.Option option = new TermSuggestion.Entry.Option(new Text("someText"), 10, 1.3f);
@@ -237,16 +257,24 @@ public void testToXContent() throws IOException {
237257
suggestion.addTerm(entry);
238258
BytesReference xContent = toXContent(suggestion, XContentType.JSON, params, randomBoolean());
239259
assertEquals(
240-
"{\"term#suggestionName\":[{"
241-
+ "\"text\":\"entryText\","
242-
+ "\"offset\":42,"
243-
+ "\"length\":313,"
244-
+ "\"options\":[{"
245-
+ "\"text\":\"someText\","
246-
+ "\"score\":1.3,"
247-
+ "\"freq\":10}]"
248-
+ "}]"
249-
+ "}", xContent.utf8ToString());
260+
("{"
261+
+ " \"term#suggestionName\": ["
262+
+ " {"
263+
+ " \"text\": \"entryText\","
264+
+ " \"offset\": 42,"
265+
+ " \"length\": 313,"
266+
+ " \"options\": ["
267+
+ " {"
268+
+ " \"text\": \"someText\","
269+
+ " \"score\": 1.3,"
270+
+ " \"freq\": 10"
271+
+ " }"
272+
+ " ]"
273+
+ " }"
274+
+ " ]"
275+
+ "}").replaceAll("\\s+", ""),
276+
xContent.utf8ToString()
277+
);
250278
}
251279
{
252280
Map<String, Set<String>> contexts = Collections.singletonMap("key", Collections.singleton("value"));
@@ -257,16 +285,28 @@ public void testToXContent() throws IOException {
257285
suggestion.addTerm(entry);
258286
BytesReference xContent = toXContent(suggestion, XContentType.JSON, params, randomBoolean());
259287
assertEquals(
260-
"{\"completion#suggestionName\":[{"
261-
+ "\"text\":\"entryText\","
262-
+ "\"offset\":42,"
263-
+ "\"length\":313,"
264-
+ "\"options\":[{"
265-
+ "\"text\":\"someText\","
266-
+ "\"score\":1.3,"
267-
+ "\"contexts\":{\"key\":[\"value\"]}"
268-
+ "}]"
269-
+ "}]}", xContent.utf8ToString());
288+
("{"
289+
+ " \"completion#suggestionName\": ["
290+
+ " {"
291+
+ " \"text\": \"entryText\","
292+
+ " \"offset\": 42,"
293+
+ " \"length\": 313,"
294+
+ " \"options\": ["
295+
+ " {"
296+
+ " \"text\": \"someText\","
297+
+ " \"score\": 1.3,"
298+
+ " \"contexts\": {"
299+
+ " \"key\": ["
300+
+ " \"value\""
301+
+ " ]"
302+
+ " }"
303+
+ " }"
304+
+ " ]"
305+
+ " }"
306+
+ " ]"
307+
+ "}").replaceAll("\\s+", ""),
308+
xContent.utf8ToString()
309+
);
270310
}
271311
}
272312
}

0 commit comments

Comments
 (0)