summaryrefslogtreecommitdiffstats
path: root/container-search/src
diff options
context:
space:
mode:
Diffstat (limited to 'container-search/src')
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Presentation.java55
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java25
-rw-r--r--container-search/src/main/java/com/yahoo/search/result/FeatureData.java14
-rw-r--r--container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java226
5 files changed, 224 insertions, 98 deletions
diff --git a/container-search/src/main/java/com/yahoo/search/query/Presentation.java b/container-search/src/main/java/com/yahoo/search/query/Presentation.java
index afa87eb4a06..b949d1edabd 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Presentation.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Presentation.java
@@ -77,6 +77,9 @@ public class Presentation implements Cloneable {
/** Whether to renders tensors in short form */
private boolean tensorShortForm = true;
+ /** Whether to renders tensors in short form */
+ private boolean tensorDirectValues = false; // TODO: Flip default on Vespa 9
+
/** Set of explicitly requested summary fields, instead of summary classes */
private Set<String> summaryFields = LazySet.newHashSet();
@@ -178,34 +181,61 @@ public class Presentation implements Cloneable {
/**
* Returns whether tensors should use short form in JSON and textual representations, see
- * <a href="https://docs.vespa.ai/en/reference/document-json-format.html#tensor">https://docs.vespa.ai/en/reference/document-json-format.html#tensor</a>
- * and <a href="https://docs.vespa.ai/en/reference/tensor.html#tensor-literal-form">https://docs.vespa.ai/en/reference/tensor.html#tensor-literal-form</a>.
+ * <a href="https://docs.vespa.ai/en/reference/document-json-format.html#tensor">https://docs.vespa.ai/en/reference/document-json-format.html#tensor</a>.
* Default is true.
*/
public boolean getTensorShortForm() { return tensorShortForm; }
+ /** @deprecated use setTensorFormat(). */
+ @Deprecated // TODO: Remove on Vespa 9
+ public void setTensorShortForm(String value) {
+ setTensorFormat(value);
+ }
/**
* Sets whether tensors should use short form in JSON and textual representations from a string.
*
* @param value a string which must be either 'short' or 'long'
* @throws IllegalArgumentException if any other value is passed
*/
- public void setTensorShortForm(String value) {
- tensorShortForm = toTensorShortForm(value);
- }
-
- private boolean toTensorShortForm(String value) {
+ public void setTensorFormat(String value) {
switch (value) {
- case "short": return true;
- case "long": return false;
- default: throw new IllegalArgumentException("Value must be 'long' or 'short', not '" + value + "'");
- }
+ case "short" :
+ tensorShortForm = true;
+ tensorDirectValues = false;
+ break;
+ case "long" :
+ tensorShortForm = false;
+ tensorDirectValues = false;
+ break;
+ case "short-value" :
+ tensorShortForm = true;
+ tensorDirectValues = true;
+ break;
+ case "long-value" :
+ tensorShortForm = false;
+ tensorDirectValues = true;
+ break;
+ default : throw new IllegalArgumentException("Value must be 'long', 'short', 'long-value', or 'short-value', not '" + value + "'");
+ };
}
public void setTensorShortForm(boolean tensorShortForm) {
this.tensorShortForm = tensorShortForm;
}
+ /**
+ * Returns whether tensor content should be rendered directly, or inside a JSON object containing a
+ * "type" entry having the tensor type, and a "cells"/"values"/"blocks" entry (depending on type),
+ * having the tensor content. See
+ * <a href="https://docs.vespa.ai/en/reference/document-json-format.html#tensor">https://docs.vespa.ai/en/reference/document-json-format.html#tensor</a>.
+ * Default is false: Render wrapped in a JSON object.
+ */
+ public boolean getTensorDirectValues() { return tensorDirectValues; }
+
+ public void setTensorDirectValues(boolean tensorDirectValues) {
+ this.tensorDirectValues = tensorDirectValues;
+ }
+
/** Prepares this for binary serialization. For internal use - see {@link Query#prepare} */
public void prepare() {
if (highlight != null)
@@ -214,8 +244,7 @@ public class Presentation implements Cloneable {
@Override
public boolean equals(Object o) {
- if ( ! (o instanceof Presentation)) return false;
- Presentation p = (Presentation) o;
+ if ( ! (o instanceof Presentation p)) return false;
return QueryHelper.equals(bolding, p.bolding) && QueryHelper.equals(summary, p.summary);
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
index d5dc8120f29..e4a83972fae 100644
--- a/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
@@ -303,7 +303,7 @@ public class QueryProperties extends Properties {
}
else if (key.size() == 3 && key.get(1).equals(Presentation.FORMAT)) {
if (key.last().equals(Presentation.TENSORS))
- query.getPresentation().setTensorShortForm(asString(value, "short"));
+ query.getPresentation().setTensorFormat(asString(value, "short")); // TODO: Switch default to short-value on Vespa 9
else
throwIllegalParameter(key.last(), Presentation.FORMAT);
}
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
index 31f99ab1927..352a31553e7 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
@@ -45,6 +45,7 @@ import com.yahoo.search.result.Hit;
import com.yahoo.search.result.HitGroup;
import com.yahoo.search.result.NanNumber;
import com.yahoo.tensor.Tensor;
+import com.yahoo.tensor.TensorType;
import com.yahoo.tensor.serialization.JsonFormat;
import java.io.IOException;
@@ -132,6 +133,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
volatile boolean jsonMapsAll = true;
volatile boolean jsonWsetsAll = false;
volatile boolean tensorShortForm = true;
+ volatile boolean tensorDirectValues = false;
boolean convertDeep() { return (jsonDeepMaps || jsonWsets); }
void init() {
this.debugRendering = false;
@@ -140,6 +142,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
this.jsonMapsAll = true;
this.jsonWsetsAll = true;
this.tensorShortForm = true;
+ this.tensorDirectValues = false;
}
void getSettings(Query q) {
if (q == null) {
@@ -154,7 +157,8 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
this.jsonMapsAll = props.getBoolean(WRAP_DEEP_MAPS, true);
this.jsonWsetsAll = props.getBoolean(WRAP_WSETS, true);
this.tensorShortForm = q.getPresentation().getTensorShortForm();
- }
+ this.tensorDirectValues = q.getPresentation().getTensorDirectValues();
+ }
}
private volatile FieldConsumerSettings fieldConsumerSettings;
@@ -776,7 +780,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
} else if (field instanceof Tensor) {
renderTensor(Optional.of((Tensor)field));
} else if (field instanceof FeatureData) {
- generator().writeRawValue(((FeatureData)field).toJson(settings.tensorShortForm));
+ generator().writeRawValue(((FeatureData)field).toJson(settings.tensorShortForm, settings.tensorDirectValues));
} else if (field instanceof Inspectable) {
renderInspectorDirect(((Inspectable)field).inspect());
} else if (field instanceof JsonProducer) {
@@ -814,24 +818,15 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
}
private void renderTensor(Optional<Tensor> tensor) throws IOException {
- if (tensor.isEmpty()) {
- generator().writeStartObject();
- generator().writeArrayFieldStart("cells");
- generator().writeEndArray();
- generator().writeEndObject();
- return;
- }
- if (settings.tensorShortForm) {
- generator().writeRawValue(new String(JsonFormat.encodeShortForm(tensor.get()), StandardCharsets.UTF_8));
- } else {
- generator().writeRawValue(new String(JsonFormat.encode(tensor.get()), StandardCharsets.UTF_8));
- }
+ generator().writeRawValue(new String(JsonFormat.encode(tensor.orElse(Tensor.Builder.of(TensorType.empty).build()),
+ settings.tensorShortForm, settings.tensorDirectValues),
+ StandardCharsets.UTF_8));
}
private JsonGenerator generator() {
if (generator == null)
throw new UnsupportedOperationException("Generator required but not assigned. " +
- "All accept() methods must be overridden when sub-classing FieldConsumer");
+ "All accept() methods must be overridden when sub-classing FieldConsumer");
return generator;
}
diff --git a/container-search/src/main/java/com/yahoo/search/result/FeatureData.java b/container-search/src/main/java/com/yahoo/search/result/FeatureData.java
index 2cb5e0e07e9..421f19475a6 100644
--- a/container-search/src/main/java/com/yahoo/search/result/FeatureData.java
+++ b/container-search/src/main/java/com/yahoo/search/result/FeatureData.java
@@ -65,16 +65,20 @@ public class FeatureData implements Inspectable, JsonProducer {
}
public String toJson(boolean tensorShortForm) {
+ return toJson(tensorShortForm, false);
+ }
+
+ public String toJson(boolean tensorShortForm, boolean tensorDirectValues) {
if (this == empty) return "{}";
if (jsonForm != null) return jsonForm;
- jsonForm = JsonRender.render(value, new Encoder(new StringBuilder(), true, tensorShortForm)).toString();
+ jsonForm = JsonRender.render(value, new Encoder(new StringBuilder(), true, tensorShortForm, tensorDirectValues)).toString();
return jsonForm;
}
@Override
public StringBuilder writeJson(StringBuilder target) {
- return JsonRender.render(value, new Encoder(target, true, false));
+ return JsonRender.render(value, new Encoder(target, true, false, false));
}
/**
@@ -173,17 +177,19 @@ public class FeatureData implements Inspectable, JsonProducer {
private static class Encoder extends JsonRender.StringEncoder {
private final boolean tensorShortForm;
+ private final boolean tensorDirectValues;
- Encoder(StringBuilder out, boolean compact, boolean tensorShortForm) {
+ Encoder(StringBuilder out, boolean compact, boolean tensorShortForm, boolean tensorDirectValues) {
super(out, compact);
this.tensorShortForm = tensorShortForm;
+ this.tensorDirectValues = tensorDirectValues;
}
@Override
public void encodeDATA(byte[] value) {
// This could be done more efficiently ...
Tensor tensor = TypedBinaryFormat.decode(Optional.empty(), GrowableByteBuffer.wrap(value));
- byte[] encodedTensor = tensorShortForm ? JsonFormat.encodeShortForm(tensor) : JsonFormat.encodeWithType(tensor);
+ byte[] encodedTensor = JsonFormat.encode(tensor, tensorShortForm, tensorDirectValues);
target().append(new String(encodedTensor, StandardCharsets.UTF_8));
}
diff --git a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
index 9486eeb92de..b3ed85911b9 100644
--- a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
@@ -53,6 +53,7 @@ import com.yahoo.slime.Slime;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorType;
import com.yahoo.tensor.serialization.TypedBinaryFormat;
+import com.yahoo.text.JSON;
import com.yahoo.text.Utf8;
import com.yahoo.yolean.Exceptions;
import com.yahoo.yolean.trace.TraceNode;
@@ -156,37 +157,136 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@Timeout(300)
- void testTensorShortForm() throws ExecutionException, InterruptedException, IOException {
- String expected = "{" +
- "\"root\":{" +
- "\"id\":\"toplevel\"," +
- "\"relevance\":1.0," +
- "\"fields\":{" +
- "\"totalCount\":1" +
- "}," +
- "\"children\":[{" +
- "\"id\":\"tensors\"," +
- "\"relevance\":1.0," +
- "\"fields\":{" +
- "\"tensor_standard\":{\"type\":\"tensor(x{},y{})\",\"cells\":[{\"address\":{\"x\":\"a\",\"y\":\"0\"},\"value\":1.0},{\"address\":{\"x\":\"b\",\"y\":\"1\"},\"value\":2.0}]}," +
- "\"tensor_indexed\":{\"type\":\"tensor(x[2],y[3])\",\"values\":[[1.0,2.0,3.0],[4.0,5.0,6.0]]}," +
- "\"tensor_single_mapped\":{\"type\":\"tensor(x{})\",\"cells\":{\"a\":1.0,\"b\":2.0}}," +
- "\"tensor_mixed\":{\"type\":\"tensor(x{},y[2])\",\"blocks\":{\"a\":[1.0,2.0],\"b\":[3.0,4.0]}}," +
- "\"summaryfeatures\":{" +
- "\"tensor_standard\":{\"type\":\"tensor(x{},y{})\",\"cells\":[{\"address\":{\"x\":\"a\",\"y\":\"0\"},\"value\":1.0},{\"address\":{\"x\":\"b\",\"y\":\"1\"},\"value\":2.0}]}," +
- "\"tensor_indexed\":{\"type\":\"tensor(x[2],y[3])\",\"values\":[[1.0,2.0,3.0],[4.0,5.0,6.0]]}," +
- "\"tensor_single_mapped\":{\"type\":\"tensor(x{})\",\"cells\":{\"a\":1.0,\"b\":2.0}}," +
- "\"tensor_mixed\":{\"type\":\"tensor(x{},y[2])\",\"blocks\":{\"a\":[1.0,2.0],\"b\":[3.0,4.0]}}" +
- "}" +
- "}" +
- "}]" +
- "}}\n";
+ void testTensorRendering() throws ExecutionException, InterruptedException, IOException {
+ String shortJson = """
+ {
+ "root": {
+ "id":"toplevel",
+ "relevance":1.0,
+ "fields":{
+ "totalCount":1
+ },
+ "children":[{
+ "id":"tensors",
+ "relevance":1.0,
+ "fields":{
+ "tensor_standard":{"type":"tensor(x{},y{})","cells":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}]},
+ "tensor_indexed":{"type":"tensor(x[2],y[3])","values":[[1.0,2.0,3.0],[4.0,5.0,6.0]]},
+ "tensor_single_mapped":{"type":"tensor(x{})","cells":{"a":1.0,"b":2.0}},
+ "tensor_mixed":{"type":"tensor(x{},y[2])","blocks":{"a":[1.0,2.0],"b":[3.0,4.0]}},
+ "summaryfeatures":{
+ "tensor_standard":{"type":"tensor(x{},y{})","cells":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}]},
+ "tensor_indexed":{"type":"tensor(x[2],y[3])","values":[[1.0,2.0,3.0],[4.0,5.0,6.0]]},
+ "tensor_single_mapped":{"type":"tensor(x{})","cells":{"a":1.0,"b":2.0}},
+ "tensor_mixed":{"type":"tensor(x{},y[2])","blocks":{"a":[1.0,2.0],"b":[3.0,4.0]}}
+ }
+ }
+ }]
+ }
+ }""";
+
+ String longJson = """
+ {
+ "root": {
+ "id":"toplevel",
+ "relevance":1.0,
+ "fields":{
+ "totalCount":1
+ },
+ "children":[{
+ "id":"tensors",
+ "relevance":1.0,
+ "fields":{
+ "tensor_standard":{"type":"tensor(x{},y{})","cells":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}]},
+ "tensor_indexed":{"type":"tensor(x[2],y[3])","cells":[{"address":{"x":"0","y":"0"},"value":1.0},{"address":{"x":"0","y":"1"},"value":2.0},{"address":{"x":"0","y":"2"},"value":3.0},{"address":{"x":"1","y":"0"},"value":4.0},{"address":{"x":"1","y":"1"},"value":5.0},{"address":{"x":"1","y":"2"},"value":6.0}]},
+ "tensor_single_mapped":{"type":"tensor(x{})","cells":[{"address":{"x":"a"},"value":1.0},{"address":{"x":"b"},"value":2.0}]},
+ "tensor_mixed":{"type":"tensor(x{},y[2])","cells":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"a","y":"1"},"value":2.0},{"address":{"x":"b","y":"0"},"value":3.0},{"address":{"x":"b","y":"1"},"value":4.0}]},
+ "summaryfeatures":{
+ "tensor_standard":{"type":"tensor(x{},y{})","cells":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}]},
+ "tensor_indexed":{"type":"tensor(x[2],y[3])","cells":[{"address":{"x":"0","y":"0"},"value":1.0},{"address":{"x":"0","y":"1"},"value":2.0},{"address":{"x":"0","y":"2"},"value":3.0},{"address":{"x":"1","y":"0"},"value":4.0},{"address":{"x":"1","y":"1"},"value":5.0},{"address":{"x":"1","y":"2"},"value":6.0}]},
+ "tensor_single_mapped":{"type":"tensor(x{})","cells":[{"address":{"x":"a"},"value":1.0},{"address":{"x":"b"},"value":2.0}]},
+ "tensor_mixed":{"type":"tensor(x{},y[2])","cells":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"a","y":"1"},"value":2.0},{"address":{"x":"b","y":"0"},"value":3.0},{"address":{"x":"b","y":"1"},"value":4.0}]}
+ }
+ }
+ }]
+ }
+ }""";
+
+ String shortDirectJson = """
+ {
+ "root": {
+ "id":"toplevel",
+ "relevance":1.0,
+ "fields":{
+ "totalCount":1
+ },
+ "children":[{
+ "id":"tensors",
+ "relevance":1.0,
+ "fields":{
+ "tensor_standard":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}],
+ "tensor_indexed":[[1.0,2.0,3.0],[4.0,5.0,6.0]],
+ "tensor_single_mapped":{"a":1.0,"b":2.0},
+ "tensor_mixed":{"a":[1.0,2.0],"b":[3.0,4.0]},
+ "summaryfeatures":{
+ "tensor_standard":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}],
+ "tensor_indexed":[[1.0,2.0,3.0],[4.0,5.0,6.0]],
+ "tensor_single_mapped":{"a":1.0,"b":2.0},
+ "tensor_mixed":{"a":[1.0,2.0],"b":[3.0,4.0]}
+ }
+ }
+ }]
+ }
+ }""";
+
+ String longDirectJson = """
+ {
+ "root": {
+ "id":"toplevel",
+ "relevance":1.0,
+ "fields":{
+ "totalCount":1
+ },
+ "children":[{
+ "id":"tensors",
+ "relevance":1.0,
+ "fields":{
+ "tensor_standard":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}],
+ "tensor_indexed":[{"address":{"x":"0","y":"0"},"value":1.0},{"address":{"x":"0","y":"1"},"value":2.0},{"address":{"x":"0","y":"2"},"value":3.0},{"address":{"x":"1","y":"0"},"value":4.0},{"address":{"x":"1","y":"1"},"value":5.0},{"address":{"x":"1","y":"2"},"value":6.0}],
+ "tensor_single_mapped":[{"address":{"x":"a"},"value":1.0},{"address":{"x":"b"},"value":2.0}],
+ "tensor_mixed":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"a","y":"1"},"value":2.0},{"address":{"x":"b","y":"0"},"value":3.0},{"address":{"x":"b","y":"1"},"value":4.0}],
+ "summaryfeatures":{
+ "tensor_standard":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"b","y":"1"},"value":2.0}],
+ "tensor_indexed":[{"address":{"x":"0","y":"0"},"value":1.0},{"address":{"x":"0","y":"1"},"value":2.0},{"address":{"x":"0","y":"2"},"value":3.0},{"address":{"x":"1","y":"0"},"value":4.0},{"address":{"x":"1","y":"1"},"value":5.0},{"address":{"x":"1","y":"2"},"value":6.0}],
+ "tensor_single_mapped":[{"address":{"x":"a"},"value":1.0},{"address":{"x":"b"},"value":2.0}],
+ "tensor_mixed":[{"address":{"x":"a","y":"0"},"value":1.0},{"address":{"x":"a","y":"1"},"value":2.0},{"address":{"x":"b","y":"0"},"value":3.0},{"address":{"x":"b","y":"1"},"value":4.0}]
+ }
+ }
+ }]
+ }
+ }""";
+
+ assertTensorRendering(shortJson, "short");
+ assertTensorRendering(longJson, "long");
+ assertTensorRendering(shortDirectJson, "short-value");
+ assertTensorRendering(longDirectJson, "long-value");
+ try {
+ render(new Result(new Query("/?presentation.format.tensors=unknown")));
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("Could not set 'presentation.format.tensors' to 'unknown': Value must be 'long', 'short', 'long-value', or 'short-value', not 'unknown'",
+ Exceptions.toMessageString(e));
+ }
+ }
+
+ private void assertTensorRendering(String expected, String format) throws ExecutionException, InterruptedException, IOException {
Slime slime = new Slime();
Cursor features = slime.setObject();
features.setData("tensor_standard", TypedBinaryFormat.encode(Tensor.from("tensor(x{},y{}):{ {x:a,y:0}:1.0, {x:b,y:1}:2.0 }")));
@@ -202,26 +302,16 @@ public class JsonRendererTestCase {
h.setField("tensor_mixed", new TensorFieldValue(Tensor.from("tensor(x{},y[2]):{a:[1,2], b:[3,4]}")));
h.setField("summaryfeatures", summaryFeatures);
- Result result1 = new Result(new Query("/?presentation.format.tensors=short"));
+ Result result1 = new Result(new Query("/?presentation.format.tensors=" + format));
result1.hits().add(h);
result1.setTotalHitCount(1L);
- String summary1 = render(result1);
- assertEqualJson(expected, summary1);
+ assertEqualJson(expected, render(result1));
- Result result2 = new Result(new Query("/?format.tensors=short"));
+ // Alias
+ Result result2 = new Result(new Query("/?format.tensors=" + format));
result2.hits().add(h);
result2.setTotalHitCount(1L);
- String summary2 = render(result2);
- assertEqualJson(expected, summary2);
-
- try {
- render(new Result(new Query("/?presentation.format.tensors=unknown")));
- fail("Expected exception");
- }
- catch (IllegalArgumentException e) {
- assertEquals("Could not set 'presentation.format.tensors' to 'unknown': Value must be 'long' or 'short', not 'unknown'",
- Exceptions.toMessageString(e));
- }
+ assertEqualJson(expected, render(result2));
}
@Test
@@ -241,7 +331,7 @@ public class JsonRendererTestCase {
+ " \"string\": \"stuff\","
+ " \"predicate\": \"a in [b]\","
+ " \"tensor1\": { \"type\": \"tensor(x{})\", \"cells\": { \"a\":2.0 } },"
- + " \"tensor2\": { \"cells\": [] },"
+ + " \"tensor2\": { \"type\": \"tensor()\", \"values\":[0.0] },"
+ " \"tensor3\": { \"type\": \"tensor(x{},y{})\", \"cells\": [ { \"address\": {\"x\": \"a\", \"y\": \"0\"}, \"value\":2.0 }, { \"address\": {\"x\": \"a\", \"y\": \"1\"}, \"value\":-1.0 } ] },"
+ " \"summaryfeatures\": {"
+ " \"scalar1\":1.5,"
@@ -281,7 +371,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
private FeatureData createSummaryFeatures() {
@@ -349,7 +439,7 @@ public class JsonRendererTestCase {
subQuery.trace("yellow", 1);
q.trace("marker", 1);
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -415,7 +505,7 @@ public class JsonRendererTestCase {
subQuery.trace(access, 1);
q.trace("marker", 1);
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -441,7 +531,7 @@ public class JsonRendererTestCase {
subQuery.trace("yellow", 1);
q.trace("marker", 1);
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@SuppressWarnings({"unchecked"})
@@ -562,7 +652,7 @@ public class JsonRendererTestCase {
execution.trace().traceNode().add(child);
q.trace("something", 1);
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -604,7 +694,7 @@ public class JsonRendererTestCase {
execution.trace().traceNode().add(child);
q.trace("something", 1);
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -653,7 +743,7 @@ public class JsonRendererTestCase {
childOfChild.add(new TraceNode("in OO languages, nesting is for birds", 0L));
execution.trace().traceNode().add(child);
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -741,7 +831,7 @@ public class JsonRendererTestCase {
r.hits().add(gg);
r.hits().addError(ErrorMessage.createInternalServerError("boom"));
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -776,7 +866,7 @@ public class JsonRendererTestCase {
r.setCoverage(new Coverage(500, 600).setDegradedReason(5));
String summary = render(execution, r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -813,7 +903,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -843,7 +933,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -871,7 +961,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -905,7 +995,7 @@ public class JsonRendererTestCase {
ErrorMessage e = new ErrorMessage(1234, "hello", "top of the day", t);
r.hits().addError(e);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -999,7 +1089,7 @@ public class JsonRendererTestCase {
r.hits().add(rg);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -1063,7 +1153,7 @@ public class JsonRendererTestCase {
r.hits().add(rg);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -1110,7 +1200,7 @@ public class JsonRendererTestCase {
h.setField("json producer", struct);
r.hits().add(h);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -1146,7 +1236,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -1172,7 +1262,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -1204,7 +1294,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -1240,7 +1330,7 @@ public class JsonRendererTestCase {
r.getElapsedTime().add(t);
renderer.setTimeSource(() -> 8L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
@Test
@@ -1278,7 +1368,7 @@ public class JsonRendererTestCase {
String json = summary.substring(jsonCallback.length() + 1, summary.length() - 2);
assertEquals(jsonCallback + "(", jsonCallbackBegin);
- assertEqualJson(expected, json);
+ assertEqualJsonContent(expected, json);
assertEquals(");", jsonCallbackEnd);
}
@@ -1327,7 +1417,7 @@ public class JsonRendererTestCase {
r.hits().add(h);
r.setTotalHitCount(1L);
String summary = render(r);
- assertEqualJson(expected, summary);
+ assertEqualJsonContent(expected, summary);
}
private static SlimeAdapter dataFromSimplified(String simplified) {
@@ -1512,8 +1602,14 @@ public class JsonRendererTestCase {
}
}
+ private void assertEqualJson(String expected, String generated) {
+ assertEquals("", validateJSON(expected));
+ assertEquals("", validateJSON(generated));
+ assertEquals(JSON.canonical(expected), JSON.canonical(generated));
+ }
+
@SuppressWarnings("unchecked")
- private void assertEqualJson(String expected, String generated) throws IOException {
+ private void assertEqualJsonContent(String expected, String generated) throws IOException {
assertEquals("", validateJSON(expected));
assertEquals("", validateJSON(generated));