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))) {