JOHNZON-367 Show a chunk of json when mapping the object fails
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/Snippet.java b/johnzon-core/src/main/java/org/apache/johnzon/core/Snippet.java
new file mode 100644
index 0000000..7e2f14a
--- /dev/null
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/Snippet.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.johnzon.core;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import javax.json.stream.JsonGenerator;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.Map;
+
+public class Snippet implements Flushable, Closeable {
+
+ private final JsonGenerator generator;
+ private final SnippetOutputStream snippet;
+
+ private Snippet(final int max) {
+ this.snippet = new SnippetOutputStream(max);
+ this.generator = Json.createGenerator(snippet);
+ }
+
+ private void write(final JsonValue value) {
+ if (snippet.isComplete()) {
+ return;
+ }
+
+ switch (value.getValueType()) {
+ case ARRAY: {
+ write(value.asJsonArray());
+ break;
+ }
+ case OBJECT: {
+ write(value.asJsonObject());
+ break;
+ }
+ default: {
+ generator.write(value);
+ }
+ }
+ }
+
+ private void write(final JsonArray array) {
+ if (snippet.isComplete()) {
+ return;
+ }
+
+ if (array.isEmpty()) {
+ generator.write(array);
+ return;
+ }
+
+ generator.writeStartArray();
+ for (final JsonValue jsonValue : array) {
+ if (snippet.isComplete()) {
+ break;
+ }
+ write(jsonValue);
+ }
+ generator.writeEnd();
+ }
+
+ private void write(final JsonObject object) {
+ if (snippet.isComplete()) {
+ return;
+ }
+
+ if (object.isEmpty()) {
+ generator.write(object);
+ return;
+ }
+
+ generator.writeStartObject();
+ for (final Map.Entry<String, JsonValue> entry : object.entrySet()) {
+ if (snippet.isComplete()) {
+ break;
+ }
+ write(entry.getKey(), entry.getValue());
+ }
+ generator.writeEnd();
+ }
+
+ private void write(final String name, final JsonValue value) {
+ if (snippet.isComplete()) {
+ return;
+ }
+
+ switch (value.getValueType()) {
+ case ARRAY:
+ generator.writeStartArray(name);
+ final JsonArray array = value.asJsonArray();
+ for (final JsonValue jsonValue : array) {
+ write(jsonValue);
+ }
+ generator.writeEnd();
+
+ break;
+ case OBJECT:
+ generator.writeStartObject(name);
+ final JsonObject object = value.asJsonObject();
+ for (final Map.Entry<String, JsonValue> keyval : object.entrySet()) {
+ write(keyval.getKey(), keyval.getValue());
+ }
+ generator.writeEnd();
+
+ break;
+ default: generator.write(name, value);
+ }
+ }
+
+ private String get() {
+ generator.close();
+ return snippet.get();
+ }
+
+ @Override
+ public void close() {
+ generator.close();
+ }
+
+ @Override
+ public void flush() {
+ generator.flush();
+ }
+
+ public static String of(final JsonValue object) {
+ return of(object, 50);
+ }
+
+ public static String of(final JsonValue value, final int max) {
+ try (final Snippet snippet = new Snippet(max)){
+ switch (value.getValueType()) {
+ case TRUE: return "true";
+ case FALSE: return "false";
+ case NULL: return "null";
+ default: {
+ snippet.write(value);
+ return snippet.get();
+ }
+ }
+ }
+ }
+
+ private static class SnippetOutputStream extends OutputStream {
+
+ private final ByteArrayOutputStream buffer;
+ private OutputStream mode;
+
+ public SnippetOutputStream(final int max) {
+ this.buffer = new ByteArrayOutputStream();
+ this.mode = new Writing(max, buffer);
+ }
+
+ public String get() {
+ if (isComplete()) {
+ return buffer.toString() + "...";
+ } else {
+ return buffer.toString();
+ }
+ }
+
+ public boolean isComplete() {
+ return mode instanceof Ignoring;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ mode.write(b);
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ mode.write(b);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len) throws IOException {
+ mode.write(b, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ mode.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mode.close();
+ }
+
+ public void print(final String string) {
+ try {
+ mode.write(string.getBytes());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ class Writing extends OutputStream {
+ private final int max;
+ private int count;
+ private final OutputStream out;
+
+ public Writing(final int max, final OutputStream out) {
+ this.max = max;
+ this.out = out;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ if (++count < max) {
+ out.write(b);
+ } else {
+ endReached();
+ }
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len) throws IOException {
+ final int remaining = max - count;
+
+ if (remaining <= 0) {
+
+ endReached();
+
+ } else if (len > remaining) {
+
+ out.write(b, off, remaining);
+ endReached();
+
+ } else {
+ out.write(b, off, len);
+ }
+ }
+
+ private void endReached() throws IOException {
+ mode = new Ignoring();
+ flush();
+ close();
+ }
+ }
+
+ static class Ignoring extends OutputStream {
+ @Override
+ public void write(final int b) throws IOException {
+ }
+ }
+
+ }
+}
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/SnippetTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/SnippetTest.java
new file mode 100644
index 0000000..7ccf90c
--- /dev/null
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/SnippetTest.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.johnzon.core;
+
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import javax.json.stream.JsonParser;
+import java.io.ByteArrayInputStream;
+
+import static org.junit.Assert.assertEquals;
+
+public class SnippetTest {
+
+ @Test
+ public void simple() {
+ final String jsonText = "{\"name\":\"string\",\"value\":\"string\",\"type\":\"string\"}";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ // This snippet is smaller than the allowed size. It should show in entirety.
+ assertEquals("{\"name\":\"string\",\"value\":\"string\",\"type\":\"string\"}", Snippet.of(object, 100));
+
+ // This snippet is exactly 50 characters when formatted. We should see no "..." at the end.
+ assertEquals("{\"name\":\"string\",\"value\":\"string\",\"type\":\"string\"}", Snippet.of(object, 50));
+
+ // This snippet is too large. We should see the "..." at the end.
+ assertEquals("{\"name\":\"string\",\"value\":\"stri...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void mapOfArray() {
+ final String jsonText = "{\"name\": [\"red\", \"green\", \"blue\"], \"value\": [\"orange\", \"yellow\", \"purple\"]}";
+
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ assertEquals("{\"name\":[\"red\",\"green\",\"blue\"],\"value\":[\"orange\",\"yellow\",\"purple\"]}", Snippet.of(object, 200));
+ assertEquals("{\"name\":[\"red\",\"green\",\"blue\"],\"value\":[\"orange\",\"...", Snippet.of(object, 50));
+ }
+
+ @Test
+ public void mapOfObject() {
+ final String jsonText = "{\"name\": {\"name\": \"red\", \"value\": \"green\", \"type\": \"blue\"}," +
+ " \"value\": {\"name\": \"orange\", \"value\": \"purple\", \"type\": \"yellow\"}}";
+
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ assertEquals("{\"name\":{\"name\":\"red\",\"value\":\"green\",\"type\":\"blue\"}," +
+ "\"value\":{\"name\":\"orange\",\"value\":\"purple\",\"type\":\"yellow\"}}", Snippet.of(object, 200));
+
+ assertEquals("{\"name\":{\"name\":\"red\",\"value\":\"green\",\"type\":\"blue\"}," +
+ "\"value\":{\"name\":\"orange\",\"value\":\"purple\",\"type...", Snippet.of(object, 100));
+ }
+
+ @Test
+ public void mapOfNestedMaps() {
+ final String jsonText = "{\"name\": {\"name\": {\"name\": {\"name\": \"red\", \"value\": \"green\", \"type\": \"blue\"}}}}";
+
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ assertEquals("{\"name\":{\"name\":{\"name\":{\"name\":\"red\"," +
+ "\"value\":\"green\",\"type\":\"blue\"}}}}", Snippet.of(object, 100));
+
+ assertEquals("{\"name\":{\"name\":{\"name\":{\"name\":\"red\",\"value\":\"gre...", Snippet.of(object, 50));
+ }
+
+ @Test
+ public void mapOfString() {
+ final String jsonText = "{\"name\":\"string\",\"value\":\"string\",\"type\":\"string\"}";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+ assertEquals("{\"name\":\"string\",\"value\":\"string\",\"type\":\"string\"}", Snippet.of(object, 50));
+ assertEquals("{\"name\":\"string\",\"value\":\"stri...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void mapOfNumber() {
+ final String jsonText = "{\"name\":1234,\"value\":5,\"type\":67890}";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ assertEquals("{\"name\":1234,\"value\":5,\"type\":67890}", Snippet.of(object, 40));
+ assertEquals("{\"name\":1234,\"value\":5,\"type\":...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void mapOfTrue() {
+ final String jsonText = "{\"name\":true,\"value\":true,\"type\":true}";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ assertEquals("{\"name\":true,\"value\":true,\"type\":true}", Snippet.of(object, 40));
+ assertEquals("{\"name\":true,\"value\":true,\"typ...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void mapOfFalse() {
+ final String jsonText = "{\"name\":false,\"value\":false,\"type\":false}";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ assertEquals("{\"name\":false,\"value\":false,\"type\":false}", Snippet.of(object, 50));
+ assertEquals("{\"name\":false,\"value\":false,\"t...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void mapOfNull() {
+ final String jsonText = "{\"name\":null,\"value\":null,\"type\":null}";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonObject object = jsonParser.getObject();
+
+ assertEquals("{\"name\":null,\"value\":null,\"type\":null}", Snippet.of(object, 50));
+ assertEquals("{\"name\":null,\"value\":null,\"typ...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void arrayOfArray() {
+ final String jsonText = "[[\"red\",\"green\"], [1,22,333], [{\"r\": 255,\"g\": 165}], [true, false]]";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("[[\"red\",\"green\"],[1,22,333],[{\"r\":255,\"g\":165}],[true,false]]", Snippet.of(object, 100));
+ assertEquals("[[\"red\",\"green\"],[1,22,333],[{\"r\":255,\"g...", Snippet.of(object, 40));
+ }
+
+ @Test
+ public void arrayOfObject() {
+ final String jsonText = "[{\"r\": 255,\"g\": \"165\"},{\"g\": 0,\"a\": \"0\"},{\"transparent\": false}]";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("[{\"r\":255,\"g\":\"165\"},{\"g\":0,\"a\":\"0\"},{\"transparent\":false}]", Snippet.of(object, 100));
+ assertEquals("[{\"r\":255,\"g\":\"165\"},{\"g\":0,\"a...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void arrayOfString() {
+ final String jsonText = "[\"red\", \"green\", \"blue\", \"orange\", \"yellow\", \"purple\"]";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("[\"red\",\"green\",\"blue\",\"orange\",\"yellow\",\"purple\"]", Snippet.of(object, 100));
+ assertEquals("[\"red\",\"green\",\"blue\",\"orange\"...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void arrayOfNumber() {
+ final String jsonText = "[1,22,333,4444,55555,666666,7777777,88888888,999999999]";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("[1,22,333,4444,55555,666666,7777777,88888888,999999999]", Snippet.of(object, 100));
+ assertEquals("[1,22,333,4444,55555,666666,77...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void arrayOfTrue() {
+ final String jsonText = "[true,true,true,true,true,true,true,true]";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("[true,true,true,true,true,true,true,true]", Snippet.of(object, 100));
+ assertEquals("[true,true,true,true,true,true...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void arrayOfFalse() {
+ final String jsonText = "[false,false,false,false,false,false,false]";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("[false,false,false,false,false,false,false]", Snippet.of(object, 100));
+ assertEquals("[false,false,false,false,false...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void arrayOfNull() {
+ final String jsonText = "[null,null,null,null,null,null]";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("[null,null,null,null,null,null]", Snippet.of(object, 50));
+ assertEquals("[null,null,null...", Snippet.of(object, 15));
+ }
+
+ @Test
+ public void string() {
+ final String jsonText = "\"This is a \\\"string\\\" with quotes in it. It should be properly escaped.\"";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("\"This is a \\\"string\\\" with quotes in it. It should be properly escaped.\"", Snippet.of(object, 100));
+ assertEquals("\"This is a \\\"string\\\" with quotes in it. It shoul...", Snippet.of(object, 50));
+ }
+
+ @Test
+ public void number() {
+ final String jsonText = "1223334444555556666667777777.88888888999999999";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("1223334444555556666667777777.88888888999999999", Snippet.of(object, 50));
+ assertEquals("1223334444555556666667777777.8...", Snippet.of(object, 30));
+ }
+
+ @Test
+ public void trueValue() {
+ final String jsonText = "true";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("true", Snippet.of(object, 50));
+ // we don't trim 'true' -- showing users something like 't...' doesn't make much sense
+ assertEquals("true", Snippet.of(object, 1));
+ }
+
+ @Test
+ public void falseValue() {
+ final String jsonText = "false";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("false", Snippet.of(object, 50));
+ // we don't trim 'false' -- showing users something like 'f...' doesn't make much sense
+ assertEquals("false", Snippet.of(object, 1));
+ }
+
+ @Test
+ public void nullValue() {
+ final String jsonText = "null";
+ final JsonParser jsonParser = Json.createParser(new ByteArrayInputStream(jsonText.getBytes()));
+ final JsonValue object = jsonParser.getValue();
+
+ assertEquals("null", Snippet.of(object, 50));
+ // we don't trim 'null' -- showing users something like 'n...' doesn't make much sense
+ assertEquals("null", Snippet.of(object, 1));
+ }
+
+}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index e376e87..300d8b5 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -18,6 +18,7 @@
*/
package org.apache.johnzon.mapper;
+import org.apache.johnzon.core.Snippet;
import org.apache.johnzon.mapper.access.AccessMode;
import org.apache.johnzon.mapper.converter.CharacterConverter;
import org.apache.johnzon.mapper.internal.AdapterKey;
@@ -346,7 +347,7 @@
}
}
if (classMapping == null) {
- throw new MapperException("Can't map " + type);
+ throw new MapperException("Can't map JSON Object to " + type + ": " + Snippet.of(object));
}
if (applyObjectConverter && classMapping.reader != null && (skippedConverters == null || !skippedConverters.contains(type))) {