Repository: eclipse-vertx/vert.x

Problem statement:
Vert.x should run base operations without requiring Jackson
In some scenario where Vert.x is embedded and used for base operation such as HTTP server and HTTP client, Jackson should not be required as long as there is no actual JSON operation such as encoding or decoding involved.

In this case, users can exclude Jackson from the classpath and have these operation still work fine.


Hints:

Patch:
diff --git a/src/main/java/io/vertx/core/buffer/Buffer.java b/src/main/java/io/vertx/core/buffer/Buffer.java
--- a/src/main/java/io/vertx/core/buffer/Buffer.java
+++ b/src/main/java/io/vertx/core/buffer/Buffer.java
@@ -17,12 +17,12 @@
 import io.vertx.codegen.annotations.GenIgnore;
 import io.vertx.codegen.annotations.VertxGen;
 import io.vertx.core.ServiceHelper;
-import io.vertx.core.json.Json;
 import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 import io.vertx.core.shareddata.Shareable;
 import io.vertx.core.shareddata.impl.ClusterSerializable;
 import io.vertx.core.spi.BufferFactory;
+import io.vertx.core.spi.json.JsonCodec;
 
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
@@ -148,7 +148,7 @@ static Buffer buffer(ByteBuf byteBuf) {
    * @return a JSON element which can be a {@link JsonArray}, {@link JsonObject}, {@link String}, ...etc if the buffer contains an array, object, string, ...etc
    */
   default Object toJson() {
-    return Json.decodeValue(this);
+    return JsonCodec.INSTANCE.fromBuffer(this, Object.class);
   }
 
   /**
diff --git a/src/main/java/io/vertx/core/json/ByteArrayDeserializer.java b/src/main/java/io/vertx/core/json/ByteArrayDeserializer.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/ByteArrayDeserializer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Base64;
+
+class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
+
+  @Override
+  public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+    String text = p.getText();
+    try {
+      return Base64.getDecoder().decode(text);
+    } catch (IllegalArgumentException e) {
+      throw new InvalidFormatException(p, "Expected a base64 encoded byte array", text, Instant.class);
+    }
+  }
+}
diff --git a/src/main/java/io/vertx/core/json/ByteArraySerializer.java b/src/main/java/io/vertx/core/json/ByteArraySerializer.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/ByteArraySerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.util.Base64;
+
+class ByteArraySerializer extends JsonSerializer<byte[]> {
+
+  @Override
+  public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+    jgen.writeString(Base64.getEncoder().encodeToString(value));
+  }
+}
diff --git a/src/main/java/io/vertx/core/json/InstantDeserializer.java b/src/main/java/io/vertx/core/json/InstantDeserializer.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/InstantDeserializer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+
+import java.io.IOException;
+import java.time.DateTimeException;
+import java.time.Instant;
+
+import static java.time.format.DateTimeFormatter.ISO_INSTANT;
+
+class InstantDeserializer extends JsonDeserializer<Instant> {
+  @Override
+  public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+    String text = p.getText();
+    try {
+      return Instant.from(ISO_INSTANT.parse(text));
+    } catch (DateTimeException e) {
+      throw new InvalidFormatException(p, "Expected an ISO 8601 formatted date time", text, Instant.class);
+    }
+  }
+}
diff --git a/src/main/java/io/vertx/core/json/InstantSerializer.java b/src/main/java/io/vertx/core/json/InstantSerializer.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/InstantSerializer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.Instant;
+
+import static java.time.format.DateTimeFormatter.ISO_INSTANT;
+
+class InstantSerializer extends JsonSerializer<Instant> {
+  @Override
+  public void serialize(Instant value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+    jgen.writeString(ISO_INSTANT.format(value));
+  }
+}
diff --git a/src/main/java/io/vertx/core/json/Json.java b/src/main/java/io/vertx/core/json/Json.java
--- a/src/main/java/io/vertx/core/json/Json.java
+++ b/src/main/java/io/vertx/core/json/Json.java
@@ -11,29 +11,16 @@
 
 package io.vertx.core.json;
 
-import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.databind.module.SimpleModule;
-import io.netty.buffer.ByteBufInputStream;
 import io.vertx.core.buffer.Buffer;
+import io.vertx.core.json.impl.JacksonCodec;
+import io.vertx.core.spi.json.JsonCodec;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.time.DateTimeException;
 import java.time.Instant;
-import java.util.Base64;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
 /**
  * @author <a href="http://tfox.org">Tim Fox</a>
@@ -44,6 +31,10 @@ public class Json {
   public static ObjectMapper prettyMapper = new ObjectMapper();
 
   static {
+    initialize();
+  }
+
+  private static void initialize() {
     // Non-standard JSON but we allow C style comments in our JSON
     mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
 
@@ -72,11 +63,7 @@ public class Json {
    * @throws EncodeException if a property cannot be encoded.
    */
   public static String encode(Object obj) throws EncodeException {
-    try {
-      return mapper.writeValueAsString(obj);
-    } catch (Exception e) {
-      throw new EncodeException("Failed to encode as JSON: " + e.getMessage());
-    }
+    return JsonCodec.INSTANCE.toString(obj);
   }
 
   /**
@@ -87,11 +74,7 @@ public static String encode(Object obj) throws EncodeException {
    * @throws EncodeException if a property cannot be encoded.
    */
   public static Buffer encodeToBuffer(Object obj) throws EncodeException {
-    try {
-      return Buffer.buffer(mapper.writeValueAsBytes(obj));
-    } catch (Exception e) {
-      throw new EncodeException("Failed to encode as JSON: " + e.getMessage());
-    }
+    return JsonCodec.INSTANCE.toBuffer(obj);
   }
 
   /**
@@ -102,11 +85,7 @@ public static Buffer encodeToBuffer(Object obj) throws EncodeException {
    * @throws EncodeException if a property cannot be encoded.
    */
   public static String encodePrettily(Object obj) throws EncodeException {
-    try {
-      return prettyMapper.writeValueAsString(obj);
-    } catch (Exception e) {
-      throw new EncodeException("Failed to encode as JSON: " + e.getMessage());
-    }
+    return JsonCodec.INSTANCE.toString(obj, true);
   }
 
   /**
@@ -118,11 +97,7 @@ public static String encodePrettily(Object obj) throws EncodeException {
    * @throws DecodeException when there is a parsing or invalid mapping.
    */
   public static <T> T decodeValue(String str, Class<T> clazz) throws DecodeException {
-    try {
-      return mapper.readValue(str, clazz);
-    } catch (Exception e) {
-      throw new DecodeException("Failed to decode: " + e.getMessage());
-    }
+    return JsonCodec.INSTANCE.fromString(str, clazz);
   }
 
   /**
@@ -134,20 +109,7 @@ public static <T> T decodeValue(String str, Class<T> clazz) throws DecodeExcepti
    * @throws DecodeException when there is a parsing or invalid mapping.
    */
   public static Object decodeValue(String str) throws DecodeException {
-    try {
-      Object value = mapper.readValue(str, Object.class);
-      if (value instanceof List) {
-        List list = (List) value;
-        return new JsonArray(list);
-      } else if (value instanceof Map) {
-        @SuppressWarnings("unchecked")
-        Map<String, Object> map = (Map<String, Object>) value;
-        return new JsonObject(map);
-      }
-      return value;
-    } catch (Exception e) {
-      throw new DecodeException("Failed to decode: " + e.getMessage());
-    }
+    return decodeValue(str, Object.class);
   }
 
   /**
@@ -159,11 +121,7 @@ public static Object decodeValue(String str) throws DecodeException {
    * @throws DecodeException when there is a parsing or invalid mapping.
    */
   public static <T> T decodeValue(String str, TypeReference<T> type) throws DecodeException {
-    try {
-      return mapper.readValue(str, type);
-    } catch (Exception e) {
-      throw new DecodeException("Failed to decode: " + e.getMessage(), e);
-    }
+    return JacksonCodec.fromString(str, type);
   }
 
   /**
@@ -175,20 +133,7 @@ public static <T> T decodeValue(String str, TypeReference<T> type) throws Decode
    * @throws DecodeException when there is a parsing or invalid mapping.
    */
   public static Object decodeValue(Buffer buf) throws DecodeException {
-    try {
-      Object value = mapper.readValue((InputStream) new ByteBufInputStream(buf.getByteBuf()), Object.class);
-      if (value instanceof List) {
-        List list = (List) value;
-        return new JsonArray(list);
-      } else if (value instanceof Map) {
-        @SuppressWarnings("unchecked")
-        Map<String, Object> map = (Map<String, Object>) value;
-        return new JsonObject(map);
-      }
-      return value;
-    } catch (Exception e) {
-      throw new DecodeException("Failed to decode: " + e.getMessage());
-    }
+    return decodeValue(buf, Object.class);
   }
 
   /**
@@ -200,11 +145,7 @@ public static Object decodeValue(Buffer buf) throws DecodeException {
    * @throws DecodeException when there is a parsing or invalid mapping.
    */
   public static <T> T decodeValue(Buffer buf, TypeReference<T> type) throws DecodeException {
-    try {
-      return mapper.readValue(new ByteBufInputStream(buf.getByteBuf()), type);
-    } catch (Exception e) {
-      throw new DecodeException("Failed to decode:" + e.getMessage(), e);
-    }
+    return JacksonCodec.fromBuffer(buf, type);
   }
 
   /**
@@ -216,113 +157,6 @@ public static <T> T decodeValue(Buffer buf, TypeReference<T> type) throws Decode
    * @throws DecodeException when there is a parsing or invalid mapping.
    */
   public static <T> T decodeValue(Buffer buf, Class<T> clazz) throws DecodeException {
-    try {
-      return mapper.readValue((InputStream) new ByteBufInputStream(buf.getByteBuf()), clazz);
-    } catch (Exception e) {
-      throw new DecodeException("Failed to decode:" + e.getMessage(), e);
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  static Object checkAndCopy(Object val, boolean copy) {
-    if (val == null) {
-      // OK
-    } else if (val instanceof Number && !(val instanceof BigDecimal)) {
-      // OK
-    } else if (val instanceof Boolean) {
-      // OK
-    } else if (val instanceof String) {
-      // OK
-    } else if (val instanceof Character) {
-      // OK
-    } else if (val instanceof CharSequence) {
-      val = val.toString();
-    } else if (val instanceof JsonObject) {
-      if (copy) {
-        val = ((JsonObject) val).copy();
-      }
-    } else if (val instanceof JsonArray) {
-      if (copy) {
-        val = ((JsonArray) val).copy();
-      }
-    } else if (val instanceof Map) {
-      if (copy) {
-        val = (new JsonObject((Map)val)).copy();
-      } else {
-        val = new JsonObject((Map)val);
-      }
-    } else if (val instanceof List) {
-      if (copy) {
-        val = (new JsonArray((List)val)).copy();
-      } else {
-        val = new JsonArray((List)val);
-      }
-    } else if (val instanceof byte[]) {
-      val = Base64.getEncoder().encodeToString((byte[])val);
-    } else if (val instanceof Instant) {
-      val = ISO_INSTANT.format((Instant) val);
-    } else {
-      throw new IllegalStateException("Illegal type in JsonObject: " + val.getClass());
-    }
-    return val;
-  }
-
-  static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
-    Iterable<T> iterable = () -> sourceIterator;
-    return StreamSupport.stream(iterable.spliterator(), false);
-  }
-
-  private static class JsonObjectSerializer extends JsonSerializer<JsonObject> {
-    @Override
-    public void serialize(JsonObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-      jgen.writeObject(value.getMap());
-    }
-  }
-
-  private static class JsonArraySerializer extends JsonSerializer<JsonArray> {
-    @Override
-    public void serialize(JsonArray value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-      jgen.writeObject(value.getList());
-    }
-  }
-
-  private static class InstantSerializer extends JsonSerializer<Instant> {
-    @Override
-    public void serialize(Instant value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-      jgen.writeString(ISO_INSTANT.format(value));
-    }
-  }
-
-  private static class InstantDeserializer extends JsonDeserializer<Instant> {
-    @Override
-    public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
-      String text = p.getText();
-      try {
-        return Instant.from(ISO_INSTANT.parse(text));
-      } catch (DateTimeException e) {
-        throw new InvalidFormatException(p, "Expected an ISO 8601 formatted date time", text, Instant.class);
-      }
-    }
-  }
-
-  private static class ByteArraySerializer extends JsonSerializer<byte[]> {
-
-    @Override
-    public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-      jgen.writeString(Base64.getEncoder().encodeToString(value));
-    }
-  }
-
-  private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
-
-    @Override
-    public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
-      String text = p.getText();
-      try {
-        return Base64.getDecoder().decode(text);
-      } catch (IllegalArgumentException e) {
-        throw new InvalidFormatException(p, "Expected a base64 encoded byte array", text, Instant.class);
-      }
-    }
+    return JsonCodec.INSTANCE.fromBuffer(buf, clazz);
   }
 }
diff --git a/src/main/java/io/vertx/core/json/JsonArray.java b/src/main/java/io/vertx/core/json/JsonArray.java
--- a/src/main/java/io/vertx/core/json/JsonArray.java
+++ b/src/main/java/io/vertx/core/json/JsonArray.java
@@ -14,6 +14,7 @@
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.shareddata.Shareable;
 import io.vertx.core.shareddata.impl.ClusterSerializable;
+import io.vertx.core.spi.json.JsonCodec;
 
 import java.time.Instant;
 import java.util.*;
@@ -289,7 +290,7 @@ public boolean hasNull(int pos) {
   /**
    * Add an enum to the JSON array.
    * <p>
-   * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@link java.lang.Enum#name}
+   * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@link java.lang.Enum#name()}
    * method and the value added as a String.
    *
    * @param value  the value
@@ -442,7 +443,7 @@ public JsonArray add(Instant value) {
    * @return  a reference to this, so the API can be used fluently
    */
   public JsonArray add(Object value) {
-    value = Json.checkAndCopy(value, false);
+    value = JsonObject.checkAndCopy(value, false);
     list.add(value);
     return this;
   }
@@ -550,7 +551,7 @@ public Iterator<Object> iterator() {
    * @return the string encoding
    */
   public String encode() {
-    return Json.encode(list);
+    return JsonCodec.INSTANCE.toString(list, false);
   }
 
   /**
@@ -559,7 +560,7 @@ public String encode() {
    * @return the buffer encoding.
    */
   public Buffer toBuffer() {
-    return Json.encodeToBuffer(list);
+    return JsonCodec.INSTANCE.toBuffer(list, false);
   }
 
   /**
@@ -568,7 +569,7 @@ public Buffer toBuffer() {
    * @return the string encoding
    */
   public String encodePrettily() {
-    return Json.encodePrettily(list);
+    return JsonCodec.INSTANCE.toString(list, true);
   }
 
   /**
@@ -580,7 +581,7 @@ public String encodePrettily() {
   public JsonArray copy() {
     List<Object> copiedList = new ArrayList<>(list.size());
     for (Object val: list) {
-      val = Json.checkAndCopy(val, true);
+      val = JsonObject.checkAndCopy(val, true);
       copiedList.add(val);
     }
     return new JsonArray(copiedList);
@@ -592,7 +593,7 @@ public JsonArray copy() {
    * @return a Stream
    */
   public Stream<Object> stream() {
-    return Json.asStream(iterator());
+    return JsonObject.asStream(iterator());
   }
 
   @Override
@@ -657,11 +658,11 @@ public int readFromBuffer(int pos, Buffer buffer) {
   }
 
   private void fromJson(String json) {
-    list = Json.decodeValue(json, List.class);
+    list = JsonCodec.INSTANCE.fromString(json, List.class);
   }
 
   private void fromBuffer(Buffer buf) {
-    list = Json.decodeValue(buf, List.class);
+    list = JsonCodec.INSTANCE.fromBuffer(buf, List.class);
   }
 
   private class Iter implements Iterator<Object> {
diff --git a/src/main/java/io/vertx/core/json/JsonArraySerializer.java b/src/main/java/io/vertx/core/json/JsonArraySerializer.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/JsonArraySerializer.java
@@ -0,0 +1,14 @@
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+class JsonArraySerializer extends JsonSerializer<JsonArray> {
+  @Override
+  public void serialize(JsonArray value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+    jgen.writeObject(value.getList());
+  }
+}
diff --git a/src/main/java/io/vertx/core/json/JsonObject.java b/src/main/java/io/vertx/core/json/JsonObject.java
--- a/src/main/java/io/vertx/core/json/JsonObject.java
+++ b/src/main/java/io/vertx/core/json/JsonObject.java
@@ -14,11 +14,14 @@
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.shareddata.Shareable;
 import io.vertx.core.shareddata.impl.ClusterSerializable;
+import io.vertx.core.spi.json.JsonCodec;
 
+import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.*;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import static java.time.format.DateTimeFormatter.ISO_INSTANT;
 
@@ -104,7 +107,7 @@ public static JsonObject mapFrom(Object obj) {
     if (obj == null) {
       return null;
     } else {
-      return new JsonObject((Map<String, Object>) Json.mapper.convertValue(obj, Map.class));
+      return new JsonObject((Map<String, Object>) JsonCodec.INSTANCE.fromValue(obj, Map.class));
     }
   }
 
@@ -118,7 +121,7 @@ public static JsonObject mapFrom(Object obj) {
    *          if the type cannot be instantiated.
    */
   public <T> T mapTo(Class<T> type) {
-    return Json.mapper.convertValue(map, type);
+    return JsonCodec.INSTANCE.fromValue(map, type);
   }
 
   /**
@@ -514,7 +517,7 @@ public Set<String> fieldNames() {
   /**
    * Put an Enum into the JSON object with the specified key.
    * <p>
-   * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@link java.lang.Enum#name}
+   * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@link java.lang.Enum#name()}
    * method and the value put as a String.
    *
    * @param key  the key
@@ -696,7 +699,7 @@ public JsonObject put(String key, Instant value) {
    */
   public JsonObject put(String key, Object value) {
     Objects.requireNonNull(key);
-    value = Json.checkAndCopy(value, false);
+    value = checkAndCopy(value, false);
     map.put(key, value);
     return this;
   }
@@ -779,7 +782,7 @@ public JsonObject mergeIn(JsonObject other, int depth) {
    * @return the string encoding.
    */
   public String encode() {
-    return Json.encode(map);
+    return JsonCodec.INSTANCE.toString(map, false);
   }
 
   /**
@@ -789,7 +792,7 @@ public String encode() {
    * @return the pretty string encoding.
    */
   public String encodePrettily() {
-    return Json.encodePrettily(map);
+    return JsonCodec.INSTANCE.toString(map, true);
   }
 
   /**
@@ -798,7 +801,7 @@ public String encodePrettily() {
    * @return the buffer encoding.
    */
   public Buffer toBuffer() {
-    return Json.encodeToBuffer(map);
+    return JsonCodec.INSTANCE.toBuffer(map, false);
   }
 
   /**
@@ -816,7 +819,7 @@ public JsonObject copy() {
     }
     for (Map.Entry<String, Object> entry: map.entrySet()) {
       Object val = entry.getValue();
-      val = Json.checkAndCopy(val, true);
+      val = checkAndCopy(val, true);
       copiedMap.put(entry.getKey(), val);
     }
     return new JsonObject(copiedMap);
@@ -839,7 +842,7 @@ public Map<String, Object> getMap() {
    * @return a stream of the entries.
    */
   public Stream<Map.Entry<String, Object>> stream() {
-    return Json.asStream(iterator());
+    return asStream(iterator());
   }
 
   /**
@@ -968,11 +971,11 @@ public int readFromBuffer(int pos, Buffer buffer) {
   }
 
   private void fromJson(String json) {
-    map = Json.decodeValue(json, Map.class);
+    map = JsonCodec.INSTANCE.fromString(json, Map.class);
   }
 
   private void fromBuffer(Buffer buf) {
-    map = Json.decodeValue(buf, Map.class);
+    map = JsonCodec.INSTANCE.fromBuffer(buf, Map.class);
   }
 
   private class Iter implements Iterator<Map.Entry<String, Object>> {
@@ -1029,4 +1032,54 @@ public Object setValue(Object value) {
       throw new UnsupportedOperationException();
     }
   }
+
+  @SuppressWarnings("unchecked")
+  static Object checkAndCopy(Object val, boolean copy) {
+    if (val == null) {
+      // OK
+    } else if (val instanceof Number && !(val instanceof BigDecimal)) {
+      // OK
+    } else if (val instanceof Boolean) {
+      // OK
+    } else if (val instanceof String) {
+      // OK
+    } else if (val instanceof Character) {
+      // OK
+    } else if (val instanceof CharSequence) {
+      val = val.toString();
+    } else if (val instanceof JsonObject) {
+      if (copy) {
+        val = ((JsonObject) val).copy();
+      }
+    } else if (val instanceof JsonArray) {
+      if (copy) {
+        val = ((JsonArray) val).copy();
+      }
+    } else if (val instanceof Map) {
+      if (copy) {
+        val = (new JsonObject((Map)val)).copy();
+      } else {
+        val = new JsonObject((Map)val);
+      }
+    } else if (val instanceof List) {
+      if (copy) {
+        val = (new JsonArray((List)val)).copy();
+      } else {
+        val = new JsonArray((List)val);
+      }
+    } else if (val instanceof byte[]) {
+      val = Base64.getEncoder().encodeToString((byte[])val);
+    } else if (val instanceof Instant) {
+      val = ISO_INSTANT.format((Instant) val);
+    } else {
+      throw new IllegalStateException("Illegal type in JsonObject: " + val.getClass());
+    }
+    return val;
+  }
+
+  static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
+    Iterable<T> iterable = () -> sourceIterator;
+    return StreamSupport.stream(iterable.spliterator(), false);
+  }
+
 }
diff --git a/src/main/java/io/vertx/core/json/JsonObjectSerializer.java b/src/main/java/io/vertx/core/json/JsonObjectSerializer.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/JsonObjectSerializer.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+class JsonObjectSerializer extends JsonSerializer<JsonObject> {
+  @Override
+  public void serialize(JsonObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+    jgen.writeObject(value.getMap());
+  }
+}
diff --git a/src/main/java/io/vertx/core/json/impl/JacksonCodec.java b/src/main/java/io/vertx/core/json/impl/JacksonCodec.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/json/impl/JacksonCodec.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+
+package io.vertx.core.json.impl;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.netty.buffer.ByteBufInputStream;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.json.DecodeException;
+import io.vertx.core.json.EncodeException;
+import io.vertx.core.json.Json;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.spi.json.JsonCodec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ */
+public class JacksonCodec implements JsonCodec {
+
+  @Override
+  public <T> T fromValue(Object json, Class<T> clazz) {
+    T value = Json.mapper.convertValue(json, clazz);
+    if (clazz == Object.class) {
+      value = (T) adapt(value);
+    }
+    return value;
+  }
+
+  public static <T> T fromValue(Object json, TypeReference<T> type) {
+    T value = Json.mapper.convertValue(json, type);
+    if (type.getType() == Object.class) {
+      value = (T) adapt(value);
+    }
+    return value;
+  }
+
+  @Override
+  public <T> T fromString(String str, Class<T> clazz) throws DecodeException {
+    return fromParser(createParser(str), clazz);
+  }
+
+  public static <T> T fromString(String str, TypeReference<T> type) throws DecodeException {
+    return fromParser(createParser(str), type);
+  }
+
+  @Override
+  public <T> T fromBuffer(Buffer buf, Class<T> clazz) throws DecodeException {
+    return fromParser(createParser(buf), clazz);
+  }
+
+  public static <T> T fromBuffer(Buffer buf, TypeReference<T> type) throws DecodeException {
+    return fromParser(createParser(buf), type);
+  }
+
+  private static JsonParser createParser(Buffer buf) {
+    try {
+      return Json.mapper.getFactory().createParser((InputStream) new ByteBufInputStream(buf.getByteBuf()));
+    } catch (IOException e) {
+      throw new DecodeException("Failed to decode:" + e.getMessage(), e);
+    }
+  }
+
+  private static JsonParser createParser(String str) {
+    try {
+      return Json.mapper.getFactory().createParser(str);
+    } catch (IOException e) {
+      throw new DecodeException("Failed to decode:" + e.getMessage(), e);
+    }
+  }
+
+  private static <T> T fromParser(JsonParser parser, Class<T> type) throws DecodeException {
+    T value;
+    try {
+      value = Json.mapper.readValue(parser, type);
+    } catch (Exception e) {
+      throw new DecodeException("Failed to decode:" + e.getMessage(), e);
+    } finally {
+      close(parser);
+    }
+    if (type == Object.class) {
+      value = (T) adapt(value);
+    }
+    return value;
+  }
+
+  private static <T> T fromParser(JsonParser parser, TypeReference<T> type) throws DecodeException {
+    T value;
+    try {
+      value = Json.mapper.readValue(parser, type);
+    } catch (Exception e) {
+      throw new DecodeException("Failed to decode:" + e.getMessage(), e);
+    } finally {
+      close(parser);
+    }
+    if (type.getType() == Object.class) {
+      value = (T) adapt(value);
+    }
+    return value;
+  }
+
+  private static void close(JsonParser parser) {
+    try {
+      parser.close();
+    } catch (IOException ignore) {
+    }
+  }
+
+  private static Object adapt(Object o) {
+    try {
+      if (o instanceof List) {
+        List list = (List) o;
+        return new JsonArray(list);
+      } else if (o instanceof Map) {
+        @SuppressWarnings("unchecked")
+        Map<String, Object> map = (Map<String, Object>) o;
+        return new JsonObject(map);
+      }
+      return o;
+    } catch (Exception e) {
+      throw new DecodeException("Failed to decode: " + e.getMessage());
+    }
+  }
+
+
+  @Override
+  public String toString(Object object, boolean pretty) throws EncodeException {
+    try {
+      ObjectMapper mapper = pretty ? Json.prettyMapper : Json.mapper;
+      return mapper.writeValueAsString(object);
+    } catch (Exception e) {
+      throw new EncodeException("Failed to encode as JSON: " + e.getMessage());
+    }
+  }
+
+  @Override
+  public Buffer toBuffer(Object object, boolean pretty) throws EncodeException {
+    try {
+      ObjectMapper mapper = pretty ? Json.prettyMapper : Json.mapper;
+      return Buffer.buffer(mapper.writeValueAsBytes(object));
+    } catch (Exception e) {
+      throw new EncodeException("Failed to encode as JSON: " + e.getMessage());
+    }
+  }
+}
diff --git a/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java b/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java
--- a/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java
+++ b/src/main/java/io/vertx/core/parsetools/impl/JsonEventImpl.java
@@ -18,8 +18,10 @@
 import io.vertx.core.json.Json;
 import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
+import io.vertx.core.json.impl.JacksonCodec;
 import io.vertx.core.parsetools.JsonEvent;
 import io.vertx.core.parsetools.JsonEventType;
+import io.vertx.core.spi.json.JsonCodec;
 
 import java.time.Instant;
 import java.util.Base64;
@@ -101,7 +103,7 @@ public <T> T mapTo(Class<T> type) {
         throw new DecodeException(e.getMessage());
       }
     } else {
-      return Json.decodeValue(String.valueOf(value), type);
+      return JsonCodec.INSTANCE.fromValue(value, type);
     }
   }
 
@@ -114,7 +116,7 @@ public <T> T mapTo(TypeReference<T> type) {
         throw new DecodeException(e.getMessage());
       }
     } else {
-      return Json.decodeValue(String.valueOf(value), type);
+      return JacksonCodec.fromValue(value, type);
     }
   }
 
diff --git a/src/main/java/io/vertx/core/spi/json/JsonCodec.java b/src/main/java/io/vertx/core/spi/json/JsonCodec.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/io/vertx/core/spi/json/JsonCodec.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+
+package io.vertx.core.spi.json;
+
+import io.vertx.core.ServiceHelper;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.json.DecodeException;
+import io.vertx.core.json.EncodeException;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ */
+public interface JsonCodec {
+
+  JsonCodec INSTANCE = ServiceHelper.loadFactory(JsonCodec.class);
+
+  /**
+   * Decode the provide {@code json} string to an object extending {@code clazz}.
+   *
+   * @param json the json string
+   * @param clazz the required object's class
+   * @return the instance
+   * @throws DecodeException anything preventing the decoding
+   */
+  <T> T fromString(String json, Class<T> clazz) throws DecodeException;
+
+  /**
+   * Like {@link #fromString(String, Class)} but with a json {@link Buffer}
+   */
+  <T> T fromBuffer(Buffer json, Class<T> clazz) throws DecodeException;
+
+  /**
+   * Like {@link #fromString(String, Class)} but with a json {@code Object}
+   */
+  <T> T fromValue(Object json, Class<T> toValueType);
+
+  /**
+   * Encode the specified {@code object} to a string.
+   */
+  default String toString(Object object) throws EncodeException {
+    return toString(object, false);
+  }
+
+  /**
+   * Encode the specified {@code object} to a string.
+   *
+   * @param object the object to encode
+   * @param pretty {@code true} to format the string prettily
+   * @return the json encoded string
+   * @throws DecodeException anything preventing the encoding
+   */
+  String toString(Object object, boolean pretty) throws EncodeException;
+
+  /**
+   * Like {@link #toString(Object, boolean)} but with a json {@link Buffer}
+   */
+  Buffer toBuffer(Object object, boolean pretty) throws EncodeException;
+
+  /**
+   * Like {@link #toString(Object)} but with a json {@link Buffer}
+   */
+  default Buffer toBuffer(Object object) throws EncodeException {
+    return toBuffer(object, false);
+  }
+}


Test patch:
diff --git a/src/test/java/io/vertx/core/json/JacksonDatabindTest.java b/src/test/java/io/vertx/core/json/JacksonDatabindTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/io/vertx/core/json/JacksonDatabindTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.test.core.TestUtils;
+import io.vertx.test.core.VertxTestBase;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.util.*;
+
+import static java.time.format.DateTimeFormatter.ISO_INSTANT;
+
+/**
+ * @author <a href="http://tfox.org">Tim Fox</a>
+ */
+public class JacksonDatabindTest extends VertxTestBase {
+
+  @Test
+  public void testGetSetMapper() {
+    ObjectMapper mapper = Json.mapper;
+    assertNotNull(mapper);
+    ObjectMapper newMapper = new ObjectMapper();
+    Json.mapper = newMapper;
+    assertSame(newMapper, Json.mapper);
+    Json.mapper = mapper;
+  }
+
+  @Test
+  public void testGetSetPrettyMapper() {
+    ObjectMapper mapper = Json.prettyMapper;
+    assertNotNull(mapper);
+    ObjectMapper newMapper = new ObjectMapper();
+    Json.prettyMapper = newMapper;
+    assertSame(newMapper, Json.prettyMapper);
+    Json.prettyMapper = mapper;
+  }
+
+  @Test
+  public void testGenericDecoding() {
+    Pojo original = new Pojo();
+    original.value = "test";
+
+    String json = Json.encode(Collections.singletonList(original));
+    List<Pojo> correct;
+
+    correct = Json.decodeValue(json, new TypeReference<List<Pojo>>() {});
+    assertTrue(((List)correct).get(0) instanceof Pojo);
+    assertEquals(original.value, correct.get(0).value);
+
+    // same must apply if instead of string we use a buffer
+    correct = Json.decodeValue(Buffer.buffer(json, "UTF8"), new TypeReference<List<Pojo>>() {});
+    assertTrue(((List)correct).get(0) instanceof Pojo);
+    assertEquals(original.value, correct.get(0).value);
+
+    List incorrect = Json.decodeValue(json, List.class);
+    assertFalse(incorrect.get(0) instanceof Pojo);
+    assertTrue(incorrect.get(0) instanceof Map);
+    assertEquals(original.value, ((Map)(incorrect.get(0))).get("value"));
+  }
+
+  @Test
+  public void testInstantDecoding() {
+    Pojo original = new Pojo();
+    original.instant = Instant.from(ISO_INSTANT.parse("2018-06-20T07:25:38.397Z"));
+    Pojo decoded = Json.decodeValue("{\"instant\":\"2018-06-20T07:25:38.397Z\"}", Pojo.class);
+    assertEquals(original.instant, decoded.instant);
+  }
+
+  @Test
+  public void testNullInstantDecoding() {
+    Pojo original = new Pojo();
+    Pojo decoded = Json.decodeValue("{\"instant\":null}", Pojo.class);
+    assertEquals(original.instant, decoded.instant);
+  }
+
+  @Test
+  public void testBytesDecoding() {
+    Pojo original = new Pojo();
+    original.bytes = TestUtils.randomByteArray(12);
+    Pojo decoded = Json.decodeValue("{\"bytes\":\"" + Base64.getEncoder().encodeToString(original.bytes) + "\"}", Pojo.class);
+    assertArrayEquals(original.bytes, decoded.bytes);
+  }
+
+  @Test
+  public void testNullBytesDecoding() {
+    Pojo original = new Pojo();
+    Pojo decoded = Json.decodeValue("{\"bytes\":null}", Pojo.class);
+    assertEquals(original.bytes, decoded.bytes);
+  }
+
+  private static class Pojo {
+    @JsonProperty
+    String value;
+    @JsonProperty
+    Instant instant;
+    @JsonProperty
+    byte[] bytes;
+  }
+}
diff --git a/src/test/java/io/vertx/core/json/JsonArrayTest.java b/src/test/java/io/vertx/core/json/JsonArrayTest.java
--- a/src/test/java/io/vertx/core/json/JsonArrayTest.java
+++ b/src/test/java/io/vertx/core/json/JsonArrayTest.java
@@ -12,7 +12,6 @@
 package io.vertx.core.json;
 
 import io.vertx.core.buffer.Buffer;
-import io.vertx.core.impl.Utils;
 import io.vertx.test.core.TestUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -792,129 +791,12 @@ public void testInvalidValsOnCopy3() {
   class SomeClass {
   }
 
-  @Test
-  public void testEncode() throws Exception {
-    jsonArray.add("foo");
-    jsonArray.add(123);
-    jsonArray.add(1234l);
-    jsonArray.add(1.23f);
-    jsonArray.add(2.34d);
-    jsonArray.add(true);
-    byte[] bytes = TestUtils.randomByteArray(10);
-    jsonArray.add(bytes);
-    jsonArray.addNull();
-    jsonArray.add(new JsonObject().put("foo", "bar"));
-    jsonArray.add(new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-    String expected = "[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]";
-    String json = jsonArray.encode();
-    assertEquals(expected, json);
-  }
-
-  @Test
-  public void testEncodeToBuffer() throws Exception {
-    jsonArray.add("foo");
-    jsonArray.add(123);
-    jsonArray.add(1234l);
-    jsonArray.add(1.23f);
-    jsonArray.add(2.34d);
-    jsonArray.add(true);
-    byte[] bytes = TestUtils.randomByteArray(10);
-    jsonArray.add(bytes);
-    jsonArray.addNull();
-    jsonArray.add(new JsonObject().put("foo", "bar"));
-    jsonArray.add(new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-    Buffer expected = Buffer.buffer("[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]", "UTF-8");
-    Buffer json = jsonArray.toBuffer();
-    assertArrayEquals(expected.getBytes(), json.getBytes());
-  }
-
-  @Test
-  public void testDecode() {
-    byte[] bytes = TestUtils.randomByteArray(10);
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-    Instant now = Instant.now();
-    String strInstant = ISO_INSTANT.format(now);
-    String json = "[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",\"" + strInstant + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]";
-    JsonArray arr = new JsonArray(json);
-    assertEquals("foo", arr.getString(0));
-    assertEquals(Integer.valueOf(123), arr.getInteger(1));
-    assertEquals(Long.valueOf(1234l), arr.getLong(2));
-    assertEquals(Float.valueOf(1.23f), arr.getFloat(3));
-    assertEquals(Double.valueOf(2.34d), arr.getDouble(4));
-    assertEquals(true, arr.getBoolean(5));
-    assertArrayEquals(bytes, arr.getBinary(6));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), arr.getValue(6));
-    assertEquals(now, arr.getInstant(7));
-    assertEquals(now.toString(), arr.getValue(7));
-    assertTrue(arr.hasNull(8));
-    JsonObject obj = arr.getJsonObject(9);
-    assertEquals("bar", obj.getString("foo"));
-    JsonArray arr2 = arr.getJsonArray(10);
-    assertEquals("foo", arr2.getString(0));
-    assertEquals(Integer.valueOf(123), arr2.getInteger(1));
-  }
-
-  @Test
-  public void testEncodePrettily() throws Exception {
-    jsonArray.add("foo");
-    jsonArray.add(123);
-    jsonArray.add(1234l);
-    jsonArray.add(1.23f);
-    jsonArray.add(2.34d);
-    jsonArray.add(true);
-    byte[] bytes = TestUtils.randomByteArray(10);
-    jsonArray.add(bytes);
-    jsonArray.addNull();
-    jsonArray.add(new JsonObject().put("foo", "bar"));
-    jsonArray.add(new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-    String expected = "[ \"foo\", 123, 1234, 1.23, 2.34, true, \"" + strBytes + "\", null, {" + Utils.LINE_SEPARATOR +
-      "  \"foo\" : \"bar\"" + Utils.LINE_SEPARATOR +
-      "}, [ \"foo\", 123 ] ]";
-    String json = jsonArray.encodePrettily();
-    assertEquals(expected, json);
-  }
-
   @Test
   public void testToString() {
     jsonArray.add("foo").add(123);
     assertEquals(jsonArray.encode(), jsonArray.toString());
   }
 
-  // Strict JSON doesn't allow comments but we do so users can add comments to config files etc
-  @Test
-  public void testCommentsInJson() {
-    String jsonWithComments =
-      "// single line comment\n" +
-        "/*\n" +
-        "  This is a multi \n" +
-        "  line comment\n" +
-        "*/\n" +
-        "[\n" +
-        "// another single line comment this time inside the JSON array itself\n" +
-        "  \"foo\", \"bar\" // and a single line comment at end of line \n" +
-        "/*\n" +
-        "  This is a another multi \n" +
-        "  line comment this time inside the JSON array itself\n" +
-        "*/\n" +
-        "]";
-    JsonArray json = new JsonArray(jsonWithComments);
-    assertEquals("[\"foo\",\"bar\"]", json.encode());
-  }
-
-  @Test
-  public void testInvalidJson() {
-    String invalid = "qiwjdoiqwjdiqwjd";
-    try {
-      new JsonArray(invalid);
-      fail();
-    } catch (DecodeException e) {
-      // OK
-    }
-  }
-
   @Test
   public void testGetList() {
     JsonObject obj = new JsonObject().put("quux", "wibble");
@@ -1085,46 +967,16 @@ private void testStreamCorrectTypes(JsonObject object) {
 
   @Test
   public void testInvalidConstruction() {
-    try {
-      new JsonArray("null");
-      fail();
-    } catch (DecodeException ignore) {
-    }
     try {
       new JsonArray((String) null);
       fail();
     } catch (NullPointerException ignore) {
     }
-    try {
-      new JsonArray("3");
-      fail();
-    } catch (DecodeException ignore) {
-    }
-    try {
-      new JsonArray("\"3");
-      fail();
-    } catch (DecodeException ignore) {
-    }
-    try {
-      new JsonArray(Buffer.buffer("null"));
-      fail();
-    } catch (DecodeException ignore) {
-    }
     try {
       new JsonArray((Buffer) null);
       fail();
     } catch (NullPointerException ignore) {
     }
-    try {
-      new JsonArray(Buffer.buffer("3"));
-      fail();
-    } catch (DecodeException ignore) {
-    }
-    try {
-      new JsonArray(Buffer.buffer("\"3"));
-      fail();
-    } catch (DecodeException ignore) {
-    }
     try {
       new JsonArray((List) null);
       fail();
diff --git a/src/test/java/io/vertx/core/json/JsonCodecTest.java b/src/test/java/io/vertx/core/json/JsonCodecTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/io/vertx/core/json/JsonCodecTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+package io.vertx.core.json;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.impl.Utils;
+import io.vertx.test.core.TestUtils;
+import org.junit.Test;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static java.time.format.DateTimeFormatter.ISO_INSTANT;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class JsonCodecTest {
+
+  private static final TypeReference<Integer> INTEGER_TYPE_REF = new TypeReference<Integer>() {};
+  private static final TypeReference<Long> LONG_TYPE_REF = new TypeReference<Long>() {};
+  private static final TypeReference<String> STRING_TYPE_REF = new TypeReference<String>() {};
+  private static final TypeReference<Float> FLOAT_TYPE_REF = new TypeReference<Float>() {};
+  private static final TypeReference<Double> DOUBLE_TYPE_REF = new TypeReference<Double>() {};
+  private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<Map<String, Object>>() {};
+  private static final TypeReference<List<Object>> LIST_TYPE_REF = new TypeReference<List<Object>>() {};
+  private static final TypeReference<Boolean> BOOLEAN_TYPE_REF = new TypeReference<Boolean>() {};
+
+  @Test
+  public void testEncodeJsonObject() {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.put("mystr", "foo");
+    jsonObject.put("mycharsequence", new StringBuilder("oob"));
+    jsonObject.put("myint", 123);
+    jsonObject.put("mylong", 1234l);
+    jsonObject.put("myfloat", 1.23f);
+    jsonObject.put("mydouble", 2.34d);
+    jsonObject.put("myboolean", true);
+    byte[] bytes = TestUtils.randomByteArray(10);
+    jsonObject.put("mybinary", bytes);
+    Instant now = Instant.now();
+    jsonObject.put("myinstant", now);
+    jsonObject.putNull("mynull");
+    jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
+    jsonObject.put("myarr", new JsonArray().add("foo").add(123));
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String expected = "{\"mystr\":\"foo\",\"mycharsequence\":\"oob\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
+      "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + ISO_INSTANT.format(now) + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}";
+    String json = jsonObject.encode();
+    assertEquals(expected, json);
+  }
+
+  @Test
+  public void testEncodeJsonArray() {
+    JsonArray jsonArray = new JsonArray();
+    jsonArray.add("foo");
+    jsonArray.add(123);
+    jsonArray.add(1234L);
+    jsonArray.add(1.23f);
+    jsonArray.add(2.34d);
+    jsonArray.add(true);
+    byte[] bytes = TestUtils.randomByteArray(10);
+    jsonArray.add(bytes);
+    jsonArray.addNull();
+    jsonArray.add(new JsonObject().put("foo", "bar"));
+    jsonArray.add(new JsonArray().add("foo").add(123));
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String expected = "[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]";
+    String json = jsonArray.encode();
+    assertEquals(expected, json);
+  }
+
+  @Test
+  public void testEncodeJsonObjectToBuffer() {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.put("mystr", "foo");
+    jsonObject.put("mycharsequence", new StringBuilder("oob"));
+    jsonObject.put("myint", 123);
+    jsonObject.put("mylong", 1234l);
+    jsonObject.put("myfloat", 1.23f);
+    jsonObject.put("mydouble", 2.34d);
+    jsonObject.put("myboolean", true);
+    byte[] bytes = TestUtils.randomByteArray(10);
+    jsonObject.put("mybinary", bytes);
+    Instant now = Instant.now();
+    jsonObject.put("myinstant", now);
+    jsonObject.putNull("mynull");
+    jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
+    jsonObject.put("myarr", new JsonArray().add("foo").add(123));
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+
+    Buffer expected = Buffer.buffer("{\"mystr\":\"foo\",\"mycharsequence\":\"oob\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
+      "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + ISO_INSTANT.format(now) + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}", "UTF-8");
+
+    Buffer json = jsonObject.toBuffer();
+    assertArrayEquals(expected.getBytes(), json.getBytes());
+  }
+
+  @Test
+  public void testEncodeJsonArrayToBuffer() {
+    JsonArray jsonArray = new JsonArray();
+    jsonArray.add("foo");
+    jsonArray.add(123);
+    jsonArray.add(1234l);
+    jsonArray.add(1.23f);
+    jsonArray.add(2.34d);
+    jsonArray.add(true);
+    byte[] bytes = TestUtils.randomByteArray(10);
+    jsonArray.add(bytes);
+    jsonArray.addNull();
+    jsonArray.add(new JsonObject().put("foo", "bar"));
+    jsonArray.add(new JsonArray().add("foo").add(123));
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    Buffer expected = Buffer.buffer("[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]", "UTF-8");
+    Buffer json = jsonArray.toBuffer();
+    assertArrayEquals(expected.getBytes(), json.getBytes());
+  }
+
+
+  @Test
+  public void testEncodeJsonObjectPrettily() {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.put("mystr", "foo");
+    jsonObject.put("myint", 123);
+    jsonObject.put("mylong", 1234l);
+    jsonObject.put("myfloat", 1.23f);
+    jsonObject.put("mydouble", 2.34d);
+    jsonObject.put("myboolean", true);
+    byte[] bytes = TestUtils.randomByteArray(10);
+    jsonObject.put("mybinary", bytes);
+    Instant now = Instant.now();
+    jsonObject.put("myinstant", now);
+    jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
+    jsonObject.put("myarr", new JsonArray().add("foo").add(123));
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String strInstant = ISO_INSTANT.format(now);
+    String expected = "{" + Utils.LINE_SEPARATOR +
+      "  \"mystr\" : \"foo\"," + Utils.LINE_SEPARATOR +
+      "  \"myint\" : 123," + Utils.LINE_SEPARATOR +
+      "  \"mylong\" : 1234," + Utils.LINE_SEPARATOR +
+      "  \"myfloat\" : 1.23," + Utils.LINE_SEPARATOR +
+      "  \"mydouble\" : 2.34," + Utils.LINE_SEPARATOR +
+      "  \"myboolean\" : true," + Utils.LINE_SEPARATOR +
+      "  \"mybinary\" : \"" + strBytes + "\"," + Utils.LINE_SEPARATOR +
+      "  \"myinstant\" : \"" + strInstant + "\"," + Utils.LINE_SEPARATOR +
+      "  \"myobj\" : {" + Utils.LINE_SEPARATOR +
+      "    \"foo\" : \"bar\"" + Utils.LINE_SEPARATOR +
+      "  }," + Utils.LINE_SEPARATOR +
+      "  \"myarr\" : [ \"foo\", 123 ]" + Utils.LINE_SEPARATOR +
+      "}";
+    String json = jsonObject.encodePrettily();
+    assertEquals(expected, json);
+  }
+
+  @Test
+  public void testEncodeJsonArrayPrettily() {
+    JsonArray jsonArray = new JsonArray();
+    jsonArray.add("foo");
+    jsonArray.add(123);
+    jsonArray.add(1234l);
+    jsonArray.add(1.23f);
+    jsonArray.add(2.34d);
+    jsonArray.add(true);
+    byte[] bytes = TestUtils.randomByteArray(10);
+    jsonArray.add(bytes);
+    jsonArray.addNull();
+    jsonArray.add(new JsonObject().put("foo", "bar"));
+    jsonArray.add(new JsonArray().add("foo").add(123));
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    String expected = "[ \"foo\", 123, 1234, 1.23, 2.34, true, \"" + strBytes + "\", null, {" + Utils.LINE_SEPARATOR +
+      "  \"foo\" : \"bar\"" + Utils.LINE_SEPARATOR +
+      "}, [ \"foo\", 123 ] ]";
+    String json = jsonArray.encodePrettily();
+    assertEquals(expected, json);
+  }
+
+  @Test
+  public void testDecodeJsonObject() {
+    byte[] bytes = TestUtils.randomByteArray(10);
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    Instant now = Instant.now();
+    String strInstant = ISO_INSTANT.format(now);
+    String json = "{\"mystr\":\"foo\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
+      "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + strInstant + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}";
+    JsonObject obj = new JsonObject(json);
+    assertEquals(json, obj.encode());
+    assertEquals("foo", obj.getString("mystr"));
+    assertEquals(Integer.valueOf(123), obj.getInteger("myint"));
+    assertEquals(Long.valueOf(1234), obj.getLong("mylong"));
+    assertEquals(Float.valueOf(1.23f), obj.getFloat("myfloat"));
+    assertEquals(Double.valueOf(2.34d), obj.getDouble("mydouble"));
+    assertTrue(obj.getBoolean("myboolean"));
+    assertArrayEquals(bytes, obj.getBinary("mybinary"));
+    assertEquals(Base64.getEncoder().encodeToString(bytes), obj.getValue("mybinary"));
+    assertEquals(now, obj.getInstant("myinstant"));
+    assertEquals(now.toString(), obj.getValue("myinstant"));
+    assertTrue(obj.containsKey("mynull"));
+    JsonObject nestedObj = obj.getJsonObject("myobj");
+    assertEquals("bar", nestedObj.getString("foo"));
+    JsonArray nestedArr = obj.getJsonArray("myarr");
+    assertEquals("foo", nestedArr.getString(0));
+    assertEquals(Integer.valueOf(123), Integer.valueOf(nestedArr.getInteger(1)));
+  }
+
+  @Test
+  public void testDecodeJsonArray() {
+    byte[] bytes = TestUtils.randomByteArray(10);
+    String strBytes = Base64.getEncoder().encodeToString(bytes);
+    Instant now = Instant.now();
+    String strInstant = ISO_INSTANT.format(now);
+    String json = "[\"foo\",123,1234,1.23,2.34,true,\"" + strBytes + "\",\"" + strInstant + "\",null,{\"foo\":\"bar\"},[\"foo\",123]]";
+    JsonArray arr = new JsonArray(json);
+    assertEquals("foo", arr.getString(0));
+    assertEquals(Integer.valueOf(123), arr.getInteger(1));
+    assertEquals(Long.valueOf(1234l), arr.getLong(2));
+    assertEquals(Float.valueOf(1.23f), arr.getFloat(3));
+    assertEquals(Double.valueOf(2.34d), arr.getDouble(4));
+    assertEquals(true, arr.getBoolean(5));
+    assertArrayEquals(bytes, arr.getBinary(6));
+    assertEquals(Base64.getEncoder().encodeToString(bytes), arr.getValue(6));
+    assertEquals(now, arr.getInstant(7));
+    assertEquals(now.toString(), arr.getValue(7));
+    assertTrue(arr.hasNull(8));
+    JsonObject obj = arr.getJsonObject(9);
+    assertEquals("bar", obj.getString("foo"));
+    JsonArray arr2 = arr.getJsonArray(10);
+    assertEquals("foo", arr2.getString(0));
+    assertEquals(Integer.valueOf(123), arr2.getInteger(1));
+  }
+
+  // Strict JSON doesn't allow comments but we do so users can add comments to config files etc
+  @Test
+  public void testDecodeJsonObjectWithComments() {
+    String jsonWithComments =
+      "// single line comment\n" +
+        "/*\n" +
+        "  This is a multi \n" +
+        "  line comment\n" +
+        "*/\n" +
+        "{\n" +
+        "// another single line comment this time inside the JSON object itself\n" +
+        "  \"foo\": \"bar\" // and a single line comment at end of line \n" +
+        "/*\n" +
+        "  This is a another multi \n" +
+        "  line comment this time inside the JSON object itself\n" +
+        "*/\n" +
+        "}";
+    JsonObject json = new JsonObject(jsonWithComments);
+    assertEquals("{\"foo\":\"bar\"}", json.encode());
+  }
+
+  // Strict JSON doesn't allow comments but we do so users can add comments to config files etc
+  @Test
+  public void testDecodeJsonArrayWithComments() {
+    String jsonWithComments =
+      "// single line comment\n" +
+        "/*\n" +
+        "  This is a multi \n" +
+        "  line comment\n" +
+        "*/\n" +
+        "[\n" +
+        "// another single line comment this time inside the JSON array itself\n" +
+        "  \"foo\", \"bar\" // and a single line comment at end of line \n" +
+        "/*\n" +
+        "  This is a another multi \n" +
+        "  line comment this time inside the JSON array itself\n" +
+        "*/\n" +
+        "]";
+    JsonArray json = new JsonArray(jsonWithComments);
+    assertEquals("[\"foo\",\"bar\"]", json.encode());
+  }
+
+  @Test
+  public void testDecodeJsonObjectWithInvalidJson() {
+    for (String test : new String[] { "null", "3", "\"3", "qiwjdoiqwjdiqwjd" }) {
+      try {
+        new JsonObject(test);
+        fail();
+      } catch (DecodeException ignore) {
+      }
+      try {
+        new JsonObject(Buffer.buffer(test));
+        fail();
+      } catch (DecodeException ignore) {
+      }
+    }
+  }
+
+  @Test
+  public void testDecodeJsonArrayWithInvalidJson() {
+    for (String test : new String[] { "null", "3", "\"3", "qiwjdoiqwjdiqwjd" }) {
+      try {
+        new JsonArray(test);
+        fail();
+      } catch (DecodeException ignore) {
+      }
+      try {
+        new JsonArray(Buffer.buffer(test));
+        fail();
+      } catch (DecodeException ignore) {
+      }
+    }
+  }
+
+  @Test
+  public void encodeCustomTypeInstant() {
+    Instant now = Instant.now();
+    String json = Json.encode(now);
+    assertNotNull(json);
+    // the RFC is one way only
+    Instant decoded = Instant.from(ISO_INSTANT.parse(json.substring(1, json.length() - 1)));
+    assertEquals(now, decoded);
+
+  }
+
+  @Test
+  public void encodeCustomTypeBinary() {
+    byte[] data = new byte[] { 'h', 'e', 'l', 'l', 'o'};
+    String json = Json.encode(data);
+    assertNotNull(json);
+    // base64 encoded hello
+    assertEquals("\"aGVsbG8=\"", json);
+  }
+
+  @Test
+  public void encodeNull() {
+    String json = Json.encode(null);
+    assertNotNull(json);
+    assertEquals("null", json);
+  }
+
+  @Test
+  public void encodeToBuffer() {
+    Buffer json = Json.encodeToBuffer("Hello World!");
+    assertNotNull(json);
+    // json strings are always UTF8
+    assertEquals("\"Hello World!\"", json.toString());
+  }
+
+  @Test
+  public void encodeNullToBuffer() {
+    Buffer json = Json.encodeToBuffer(null);
+    assertNotNull(json);
+    assertEquals("null", json.toString());
+  }
+
+  @Test
+  public void testDecodeValue() {
+    assertDecodeValue(Buffer.buffer("42"), 42, INTEGER_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("42"), 42L, LONG_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("\"foobar\""), "foobar", STRING_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("3.4"), 3.4f, FLOAT_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("3.4"), 3.4d, DOUBLE_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("{\"foo\":4}"), Collections.singletonMap("foo", 4), MAP_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("[0,1,2]"), Arrays.asList(0, 1, 2), LIST_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("true"), true, BOOLEAN_TYPE_REF);
+    assertDecodeValue(Buffer.buffer("false"), false, BOOLEAN_TYPE_REF);
+  }
+
+  private <T> void assertDecodeValue(Buffer buffer, T expected, TypeReference<T> ref) {
+    Type type = ref.getType();
+    Class<?> clazz = type instanceof Class ? (Class<?>) type : (Class<?>) ((ParameterizedType) type).getRawType();
+    assertEquals(expected, Json.decodeValue(buffer, clazz));
+    assertEquals(expected, Json.decodeValue(buffer, ref));
+    assertEquals(expected, Json.decodeValue(buffer.toString(StandardCharsets.UTF_8), clazz));
+    assertEquals(expected, Json.decodeValue(buffer.toString(StandardCharsets.UTF_8), ref));
+    Buffer nullValue = Buffer.buffer("null");
+    assertNull(Json.decodeValue(nullValue, clazz));
+    assertNull(Json.decodeValue(nullValue, ref));
+    assertNull(Json.decodeValue(nullValue.toString(StandardCharsets.UTF_8), clazz));
+    assertNull(Json.decodeValue(nullValue.toString(StandardCharsets.UTF_8), ref));
+  }
+
+  @Test
+  public void testDecodeBufferUnknowContent() {
+    testDecodeUnknowContent(true);
+  }
+
+  @Test
+  public void testDecodeStringUnknowContent() {
+    testDecodeUnknowContent(false);
+  }
+
+  private void testDecodeUnknowContent(boolean asBuffer) {
+    String number = String.valueOf(1);
+    assertEquals(1, asBuffer ? Json.decodeValue(Buffer.buffer(number)) : Json.decodeValue(number));
+
+    String bool = Boolean.TRUE.toString();
+    assertEquals(true, asBuffer ? Json.decodeValue(Buffer.buffer(bool)) : Json.decodeValue(bool));
+
+    String text = "\"whatever\"";
+    assertEquals("whatever", asBuffer ? Json.decodeValue(Buffer.buffer(text)) : Json.decodeValue(text));
+
+    String nullText = "null";
+    assertNull(asBuffer ? Json.decodeValue(Buffer.buffer(nullText)) : Json.decodeValue(nullText));
+
+    JsonObject obj = new JsonObject().put("foo", "bar");
+    assertEquals(obj, asBuffer ? Json.decodeValue(obj.toBuffer()) : Json.decodeValue(obj.toString()));
+
+    JsonArray arr = new JsonArray().add(1).add(false).add("whatever").add(obj);
+    assertEquals(arr, asBuffer ? Json.decodeValue(arr.toBuffer()) : Json.decodeValue(arr.toString()));
+
+    String invalidText = "\"invalid";
+    try {
+      if (asBuffer) {
+        Json.decodeValue(Buffer.buffer(invalidText));
+      } else {
+        Json.decodeValue(invalidText);
+      }
+      fail();
+    } catch (DecodeException ignore) {
+    }
+  }
+}
diff --git a/src/test/java/io/vertx/core/json/JsonMapperTest.java b/src/test/java/io/vertx/core/json/JsonMapperTest.java
deleted file mode 100644
--- a/src/test/java/io/vertx/core/json/JsonMapperTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (c) 2014 Red Hat, Inc. and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
- * which is available at https://www.apache.org/licenses/LICENSE-2.0.
- *
- * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
- */
-
-package io.vertx.core.json;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.vertx.core.buffer.Buffer;
-import io.vertx.test.core.TestUtils;
-import io.vertx.test.core.VertxTestBase;
-import org.junit.Test;
-
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.util.*;
-
-import static java.time.format.DateTimeFormatter.ISO_INSTANT;
-
-/**
- * @author <a href="http://tfox.org">Tim Fox</a>
- */
-public class JsonMapperTest extends VertxTestBase {
-
-  @Test
-  public void testGetSetMapper() {
-    ObjectMapper mapper = Json.mapper;
-    assertNotNull(mapper);
-    ObjectMapper newMapper = new ObjectMapper();
-    Json.mapper = newMapper;
-    assertSame(newMapper, Json.mapper);
-    Json.mapper = mapper;
-  }
-
-  @Test
-  public void testGetSetPrettyMapper() {
-    ObjectMapper mapper = Json.prettyMapper;
-    assertNotNull(mapper);
-    ObjectMapper newMapper = new ObjectMapper();
-    Json.prettyMapper = newMapper;
-    assertSame(newMapper, Json.prettyMapper);
-    Json.prettyMapper = mapper;
-  }
-
-  @Test
-  public void encodeCustomTypeInstant() {
-    Instant now = Instant.now();
-    String json = Json.encode(now);
-    assertNotNull(json);
-    // the RFC is one way only
-    Instant decoded = Instant.from(ISO_INSTANT.parse(json.substring(1, json.length() - 1)));
-    assertEquals(now, decoded);
-
-  }
-
-  @Test
-  public void encodeCustomTypeInstantNull() {
-    Instant now = null;
-    String json = Json.encode(now);
-    assertNotNull(json);
-    assertEquals("null", json);
-  }
-
-  @Test
-  public void encodeCustomTypeBinary() {
-    byte[] data = new byte[] { 'h', 'e', 'l', 'l', 'o'};
-    String json = Json.encode(data);
-    assertNotNull(json);
-    // base64 encoded hello
-    assertEquals("\"aGVsbG8=\"", json);
-  }
-
-  @Test
-  public void encodeCustomTypeBinaryNull() {
-    byte[] data = null;
-    String json = Json.encode(data);
-    assertNotNull(json);
-    assertEquals("null", json);
-  }
-
-  @Test
-  public void encodeToBuffer() {
-    Buffer json = Json.encodeToBuffer("Hello World!");
-    assertNotNull(json);
-    // json strings are always UTF8
-    assertEquals("\"Hello World!\"", json.toString("UTF-8"));
-  }
-
-  @Test
-  public void testGenericDecoding() {
-    Pojo original = new Pojo();
-    original.value = "test";
-
-    String json = Json.encode(Collections.singletonList(original));
-    List<Pojo> correct;
-
-    correct = Json.decodeValue(json, new TypeReference<List<Pojo>>() {});
-    assertTrue(((List)correct).get(0) instanceof Pojo);
-    assertEquals(original.value, correct.get(0).value);
-
-    // same must apply if instead of string we use a buffer
-    correct = Json.decodeValue(Buffer.buffer(json, "UTF8"), new TypeReference<List<Pojo>>() {});
-    assertTrue(((List)correct).get(0) instanceof Pojo);
-    assertEquals(original.value, correct.get(0).value);
-
-    List incorrect = Json.decodeValue(json, List.class);
-    assertFalse(incorrect.get(0) instanceof Pojo);
-    assertTrue(incorrect.get(0) instanceof Map);
-    assertEquals(original.value, ((Map)(incorrect.get(0))).get("value"));
-  }
-
-  @Test
-  public void testInstantDecoding() {
-    Pojo original = new Pojo();
-    original.instant = Instant.from(ISO_INSTANT.parse("2018-06-20T07:25:38.397Z"));
-    Pojo decoded = Json.decodeValue("{\"instant\":\"2018-06-20T07:25:38.397Z\"}", Pojo.class);
-    assertEquals(original.instant, decoded.instant);
-  }
-
-  @Test
-  public void testNullInstantDecoding() {
-    Pojo original = new Pojo();
-    Pojo decoded = Json.decodeValue("{\"instant\":null}", Pojo.class);
-    assertEquals(original.instant, decoded.instant);
-  }
-
-  @Test
-  public void testBytesDecoding() {
-    Pojo original = new Pojo();
-    original.bytes = TestUtils.randomByteArray(12);
-    Pojo decoded = Json.decodeValue("{\"bytes\":\"" + Base64.getEncoder().encodeToString(original.bytes) + "\"}", Pojo.class);
-    assertArrayEquals(original.bytes, decoded.bytes);
-  }
-
-  @Test
-  public void testNullBytesDecoding() {
-    Pojo original = new Pojo();
-    Pojo decoded = Json.decodeValue("{\"bytes\":null}", Pojo.class);
-    assertEquals(original.bytes, decoded.bytes);
-  }
-
-  private static class Pojo {
-    @JsonProperty
-    String value;
-    @JsonProperty
-    Instant instant;
-    @JsonProperty
-    byte[] bytes;
-  }
-
-  private static final TypeReference<Integer> INTEGER_TYPE_REF = new TypeReference<Integer>() {};
-  private static final TypeReference<Long> LONG_TYPE_REF = new TypeReference<Long>() {};
-  private static final TypeReference<String> STRING_TYPE_REF = new TypeReference<String>() {};
-  private static final TypeReference<Float> FLOAT_TYPE_REF = new TypeReference<Float>() {};
-  private static final TypeReference<Double> DOUBLE_TYPE_REF = new TypeReference<Double>() {};
-  private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<Map<String, Object>>() {};
-  private static final TypeReference<List<Object>> LIST_TYPE_REF = new TypeReference<List<Object>>() {};
-  private static final TypeReference<Boolean> BOOLEAN_TYPE_REF = new TypeReference<Boolean>() {};
-
-  @Test
-  public void testDecodeValue() {
-    assertDecodeValue(Buffer.buffer("42"), 42, INTEGER_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("42"), 42L, LONG_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("\"foobar\""), "foobar", STRING_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("3.4"), 3.4f, FLOAT_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("3.4"), 3.4d, DOUBLE_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("{\"foo\":4}"), Collections.singletonMap("foo", 4), MAP_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("[0,1,2]"), Arrays.asList(0, 1, 2), LIST_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("true"), true, BOOLEAN_TYPE_REF);
-    assertDecodeValue(Buffer.buffer("false"), false, BOOLEAN_TYPE_REF);
-  }
-
-  private <T> void assertDecodeValue(Buffer buffer, T expected, TypeReference<T> ref) {
-    Type type = ref.getType();
-    Class<?> clazz = type instanceof Class ? (Class<?>) type : (Class<?>) ((ParameterizedType) type).getRawType();
-    assertEquals(expected, Json.decodeValue(buffer, clazz));
-    assertEquals(expected, Json.decodeValue(buffer, ref));
-    assertEquals(expected, Json.decodeValue(buffer.toString(StandardCharsets.UTF_8), clazz));
-    assertEquals(expected, Json.decodeValue(buffer.toString(StandardCharsets.UTF_8), ref));
-    Buffer nullValue = Buffer.buffer("null");
-    assertNull(Json.decodeValue(nullValue, clazz));
-    assertNull(Json.decodeValue(nullValue, ref));
-    assertNull(Json.decodeValue(nullValue.toString(StandardCharsets.UTF_8), clazz));
-    assertNull(Json.decodeValue(nullValue.toString(StandardCharsets.UTF_8), ref));
-  }
-
-  @Test
-  public void testDecodeBufferUnknowContent() {
-    testDecodeUnknowContent(true);
-  }
-
-  @Test
-  public void testDecodeStringUnknowContent() {
-    testDecodeUnknowContent(false);
-  }
-
-  private void testDecodeUnknowContent(boolean asBuffer) {
-    String number = String.valueOf(1);
-    assertEquals(1, asBuffer ? Json.decodeValue(Buffer.buffer(number)) : Json.decodeValue(number));
-
-    String bool = Boolean.TRUE.toString();
-    assertEquals(true, asBuffer ? Json.decodeValue(Buffer.buffer(bool)) : Json.decodeValue(bool));
-
-    String text = "\"whatever\"";
-    assertEquals("whatever", asBuffer ? Json.decodeValue(Buffer.buffer(text)) : Json.decodeValue(text));
-
-    String nullText = "null";
-    assertNull(asBuffer ? Json.decodeValue(Buffer.buffer(nullText)) : Json.decodeValue(nullText));
-
-    JsonObject obj = new JsonObject().put("foo", "bar");
-    assertEquals(obj, asBuffer ? Json.decodeValue(obj.toBuffer()) : Json.decodeValue(obj.toString()));
-
-    JsonArray arr = new JsonArray().add(1).add(false).add("whatever").add(obj);
-    assertEquals(arr, asBuffer ? Json.decodeValue(arr.toBuffer()) : Json.decodeValue(arr.toString()));
-
-    String invalidText = "\"invalid";
-    try {
-      if (asBuffer) {
-        Json.decodeValue(Buffer.buffer(invalidText));
-      } else {
-        Json.decodeValue(invalidText);
-      }
-      fail();
-    } catch (DecodeException ignore) {
-    }
-  }
-}
diff --git a/src/test/java/io/vertx/core/json/JsonObjectTest.java b/src/test/java/io/vertx/core/json/JsonObjectTest.java
--- a/src/test/java/io/vertx/core/json/JsonObjectTest.java
+++ b/src/test/java/io/vertx/core/json/JsonObjectTest.java
@@ -12,7 +12,6 @@
 package io.vertx.core.json;
 
 import io.vertx.core.buffer.Buffer;
-import io.vertx.core.impl.Utils;
 import io.vertx.test.core.TestUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -1182,8 +1181,8 @@ public void testMergeIn2() {
 
   @Test
   public void testMergeInDepth0() {
-    JsonObject obj1 = new JsonObject("{ \"foo\": { \"bar\": \"flurb\" }}");
-    JsonObject obj2 = new JsonObject("{ \"foo\": { \"bar\": \"eek\" }}");
+    JsonObject obj1 = new JsonObject().put("foo", new JsonObject().put("bar", "flurb"));
+    JsonObject obj2 = new JsonObject().put("foo", new JsonObject().put("bar", "eek"));
     obj1.mergeIn(obj2, 0);
     assertEquals(1, obj1.size());
     assertEquals(1, obj1.getJsonObject("foo").size());
@@ -1192,8 +1191,8 @@ public void testMergeInDepth0() {
 
   @Test
   public void testMergeInFlat() {
-    JsonObject obj1 = new JsonObject("{ \"foo\": { \"bar\": \"flurb\", \"eek\": 32 }}");
-    JsonObject obj2 = new JsonObject("{ \"foo\": { \"bar\": \"eek\" }}");
+    JsonObject obj1 = new JsonObject().put("foo", new JsonObject().put("bar", "flurb").put("eek", 32));
+    JsonObject obj2 = new JsonObject().put("foo", new JsonObject().put("bar", "eek"));
     obj1.mergeIn(obj2, false);
     assertEquals(1, obj1.size());
     assertEquals(1, obj1.getJsonObject("foo").size());
@@ -1202,8 +1201,8 @@ public void testMergeInFlat() {
 
   @Test
   public void testMergeInDepth1() {
-    JsonObject obj1 = new JsonObject("{ \"foo\": \"bar\", \"flurb\": { \"eek\": \"foo\", \"bar\": \"flurb\"}}");
-    JsonObject obj2 = new JsonObject("{ \"flurb\": { \"bar\": \"flurb1\" }}");
+    JsonObject obj1 = new JsonObject().put("foo", "bar").put("flurb", new JsonObject().put("eek", "foo").put("bar", "flurb"));
+    JsonObject obj2 = new JsonObject().put("flurb", new JsonObject().put("bar", "flurb1"));
     obj1.mergeIn(obj2, 1);
     assertEquals(2, obj1.size());
     assertEquals(1, obj1.getJsonObject("flurb").size());
@@ -1212,8 +1211,8 @@ public void testMergeInDepth1() {
 
   @Test
   public void testMergeInDepth2() {
-    JsonObject obj1 = new JsonObject("{ \"foo\": \"bar\", \"flurb\": { \"eek\": \"foo\", \"bar\": \"flurb\"}}");
-    JsonObject obj2 = new JsonObject("{ \"flurb\": { \"bar\": \"flurb1\" }}");
+    JsonObject obj1 = new JsonObject().put("foo", "bar").put("flurb", new JsonObject().put("eek", "foo").put("bar", "flurb"));
+    JsonObject obj2 = new JsonObject().put("flurb", new JsonObject().put("bar", "flurb1"));
     obj1.mergeIn(obj2, 2);
     assertEquals(2, obj1.size());
     assertEquals(2, obj1.getJsonObject("flurb").size());
@@ -1221,154 +1220,12 @@ public void testMergeInDepth2() {
     assertEquals("flurb1", obj1.getJsonObject("flurb").getString("bar"));
   }
 
-  @Test
-  public void testEncode() throws Exception {
-    jsonObject.put("mystr", "foo");
-    jsonObject.put("mycharsequence", new StringBuilder("oob"));
-    jsonObject.put("myint", 123);
-    jsonObject.put("mylong", 1234l);
-    jsonObject.put("myfloat", 1.23f);
-    jsonObject.put("mydouble", 2.34d);
-    jsonObject.put("myboolean", true);
-    byte[] bytes = TestUtils.randomByteArray(10);
-    jsonObject.put("mybinary", bytes);
-    Instant now = Instant.now();
-    jsonObject.put("myinstant", now);
-    jsonObject.putNull("mynull");
-    jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
-    jsonObject.put("myarr", new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-    String expected = "{\"mystr\":\"foo\",\"mycharsequence\":\"oob\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
-      "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + ISO_INSTANT.format(now) + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}";
-    String json = jsonObject.encode();
-    assertEquals(expected, json);
-  }
-
-  @Test
-  public void testEncodeToBuffer() throws Exception {
-    jsonObject.put("mystr", "foo");
-    jsonObject.put("mycharsequence", new StringBuilder("oob"));
-    jsonObject.put("myint", 123);
-    jsonObject.put("mylong", 1234l);
-    jsonObject.put("myfloat", 1.23f);
-    jsonObject.put("mydouble", 2.34d);
-    jsonObject.put("myboolean", true);
-    byte[] bytes = TestUtils.randomByteArray(10);
-    jsonObject.put("mybinary", bytes);
-    Instant now = Instant.now();
-    jsonObject.put("myinstant", now);
-    jsonObject.putNull("mynull");
-    jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
-    jsonObject.put("myarr", new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-
-    Buffer expected = Buffer.buffer("{\"mystr\":\"foo\",\"mycharsequence\":\"oob\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
-      "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + ISO_INSTANT.format(now) + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}", "UTF-8");
-
-    Buffer json = jsonObject.toBuffer();
-    assertArrayEquals(expected.getBytes(), json.getBytes());
-  }
-
-  @Test
-  public void testDecode() throws Exception {
-    byte[] bytes = TestUtils.randomByteArray(10);
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-    Instant now = Instant.now();
-    String strInstant = ISO_INSTANT.format(now);
-    String json = "{\"mystr\":\"foo\",\"myint\":123,\"mylong\":1234,\"myfloat\":1.23,\"mydouble\":2.34,\"" +
-      "myboolean\":true,\"mybinary\":\"" + strBytes + "\",\"myinstant\":\"" + strInstant + "\",\"mynull\":null,\"myobj\":{\"foo\":\"bar\"},\"myarr\":[\"foo\",123]}";
-    JsonObject obj = new JsonObject(json);
-    assertEquals(json, obj.encode());
-    assertEquals("foo", obj.getString("mystr"));
-    assertEquals(Integer.valueOf(123), obj.getInteger("myint"));
-    assertEquals(Long.valueOf(1234), obj.getLong("mylong"));
-    assertEquals(Float.valueOf(1.23f), obj.getFloat("myfloat"));
-    assertEquals(Double.valueOf(2.34d), obj.getDouble("mydouble"));
-    assertTrue(obj.getBoolean("myboolean"));
-    assertArrayEquals(bytes, obj.getBinary("mybinary"));
-    assertEquals(Base64.getEncoder().encodeToString(bytes), obj.getValue("mybinary"));
-    assertEquals(now, obj.getInstant("myinstant"));
-    assertEquals(now.toString(), obj.getValue("myinstant"));
-    assertTrue(obj.containsKey("mynull"));
-    JsonObject nestedObj = obj.getJsonObject("myobj");
-    assertEquals("bar", nestedObj.getString("foo"));
-    JsonArray nestedArr = obj.getJsonArray("myarr");
-    assertEquals("foo", nestedArr.getString(0));
-    assertEquals(Integer.valueOf(123), Integer.valueOf(nestedArr.getInteger(1)));
-  }
-
   @Test
   public void testToString() {
     jsonObject.put("foo", "bar");
     assertEquals(jsonObject.encode(), jsonObject.toString());
   }
 
-  @Test
-  public void testEncodePrettily() throws Exception {
-    jsonObject.put("mystr", "foo");
-    jsonObject.put("myint", 123);
-    jsonObject.put("mylong", 1234l);
-    jsonObject.put("myfloat", 1.23f);
-    jsonObject.put("mydouble", 2.34d);
-    jsonObject.put("myboolean", true);
-    byte[] bytes = TestUtils.randomByteArray(10);
-    jsonObject.put("mybinary", bytes);
-    Instant now = Instant.now();
-    jsonObject.put("myinstant", now);
-    jsonObject.put("myobj", new JsonObject().put("foo", "bar"));
-    jsonObject.put("myarr", new JsonArray().add("foo").add(123));
-    String strBytes = Base64.getEncoder().encodeToString(bytes);
-    String strInstant = ISO_INSTANT.format(now);
-    String expected = "{" + Utils.LINE_SEPARATOR +
-      "  \"mystr\" : \"foo\"," + Utils.LINE_SEPARATOR +
-      "  \"myint\" : 123," + Utils.LINE_SEPARATOR +
-      "  \"mylong\" : 1234," + Utils.LINE_SEPARATOR +
-      "  \"myfloat\" : 1.23," + Utils.LINE_SEPARATOR +
-      "  \"mydouble\" : 2.34," + Utils.LINE_SEPARATOR +
-      "  \"myboolean\" : true," + Utils.LINE_SEPARATOR +
-      "  \"mybinary\" : \"" + strBytes + "\"," + Utils.LINE_SEPARATOR +
-      "  \"myinstant\" : \"" + strInstant + "\"," + Utils.LINE_SEPARATOR +
-      "  \"myobj\" : {" + Utils.LINE_SEPARATOR +
-      "    \"foo\" : \"bar\"" + Utils.LINE_SEPARATOR +
-      "  }," + Utils.LINE_SEPARATOR +
-      "  \"myarr\" : [ \"foo\", 123 ]" + Utils.LINE_SEPARATOR +
-      "}";
-    String json = jsonObject.encodePrettily();
-    assertEquals(expected, json);
-  }
-
-  // Strict JSON doesn't allow comments but we do so users can add comments to config files etc
-  @Test
-  public void testCommentsInJson() {
-    String jsonWithComments =
-      "// single line comment\n" +
-      "/*\n" +
-      "  This is a multi \n" +
-      "  line comment\n" +
-      "*/\n" +
-      "{\n" +
-      "// another single line comment this time inside the JSON object itself\n" +
-      "  \"foo\": \"bar\" // and a single line comment at end of line \n" +
-      "/*\n" +
-      "  This is a another multi \n" +
-      "  line comment this time inside the JSON object itself\n" +
-      "*/\n" +
-      "}";
-    JsonObject json = new JsonObject(jsonWithComments);
-    assertEquals("{\"foo\":\"bar\"}", json.encode());
-  }
-
-  @Test
-  public void testInvalidJson() {
-    String invalid = "qiwjdoiqwjdiqwjd";
-    try {
-      new JsonObject(invalid);
-      fail();
-    } catch (DecodeException e) {
-      // OK
-    }
-  }
-
   @Test
   public void testClear() {
     jsonObject.put("foo", "bar");
@@ -1830,46 +1687,16 @@ private JsonObject createJsonObject() {
 
   @Test
   public void testInvalidConstruction() {
-    try {
-      new JsonObject("null");
-      fail();
-    } catch (DecodeException ignore) {
-    }
     try {
       new JsonObject((String) null);
       fail();
     } catch (NullPointerException ignore) {
     }
-    try {
-      new JsonObject("3");
-      fail();
-    } catch (DecodeException ignore) {
-    }
-    try {
-      new JsonObject("\"3");
-      fail();
-    } catch (DecodeException ignore) {
-    }
-    try {
-      new JsonObject(Buffer.buffer("null"));
-      fail();
-    } catch (DecodeException ignore) {
-    }
     try {
       new JsonObject((Buffer) null);
       fail();
     } catch (NullPointerException ignore) {
     }
-    try {
-      new JsonObject(Buffer.buffer("3"));
-      fail();
-    } catch (DecodeException ignore) {
-    }
-    try {
-      new JsonObject(Buffer.buffer("\"3"));
-      fail();
-    } catch (DecodeException ignore) {
-    }
     try {
       new JsonObject((Map) null);
       fail();
diff --git a/src/test/java/io/vertx/it/JsonTest.java b/src/test/java/io/vertx/it/JsonTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/io/vertx/it/JsonTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ */
+
+package io.vertx.it;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.eventbus.EventBus;
+import io.vertx.core.http.HttpClient;
+import io.vertx.core.http.HttpClientOptions;
+import io.vertx.core.http.HttpServerOptions;
+import io.vertx.core.http.HttpTestBase;
+import io.vertx.core.impl.VertxInternal;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.net.OpenSSLEngineOptions;
+import io.vertx.test.core.VertxTestBase;
+import io.vertx.test.tls.Cert;
+import io.vertx.test.tls.Trust;
+import org.junit.Test;
+
+import static io.vertx.core.http.HttpTestBase.DEFAULT_HTTP_HOST;
+import static io.vertx.core.http.HttpTestBase.DEFAULT_HTTP_PORT;
+
+/**
+ * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
+ */
+public class JsonTest extends VertxTestBase {
+
+  @Test
+  public void testJsonObject() {
+    JsonObject obj = new JsonObject();
+    obj.put("foo", "bar");
+    try {
+      obj.toString();
+      fail();
+    } catch (NoClassDefFoundError ignore) {
+    }
+    assertTrue(obj.containsKey("foo"));
+    assertEquals(obj, obj.copy());
+  }
+
+  @Test
+  public void testJsonArray() {
+    JsonArray array = new JsonArray();
+    array.add("foo");
+    try {
+      array.toString();
+      fail();
+    } catch (NoClassDefFoundError ignore) {
+    }
+    assertTrue(array.contains("foo"));
+    assertEquals(array, array.copy());
+  }
+
+  @Test
+  public void testHttp() {
+    Vertx vertx = Vertx.vertx();
+    try {
+      vertx.createHttpServer().requestHandler(req -> {
+        req.response().end("hello");
+      }).listen(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, onSuccess(s -> {
+        HttpClient client = vertx.createHttpClient();
+        client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/", resp -> {
+          resp.exceptionHandler(this::fail);
+          resp.bodyHandler(body -> {
+            assertEquals("hello", body.toString());
+            testComplete();
+          });
+        }).exceptionHandler(this::fail)
+          .end();
+      }));
+      await();
+    } finally {
+      vertx.close();
+    }
+  }
+
+  @Test
+  public void testEventBus() {
+    Vertx vertx = Vertx.vertx();
+    try {
+      EventBus eb = vertx.eventBus();
+      eb.consumer("the-address", msg -> {
+        assertEquals("ping", msg.body());
+        msg.reply("pong");
+      });
+      eb.request("the-address", "ping", onSuccess(resp -> {
+        assertEquals("pong", resp.body());
+        testComplete();
+      }));
+      await();
+    } finally {
+      vertx.close();
+    }
+  }
+}