Jackson should not be required to run OOTB for operations that don't require json encode/decode operations

This commit is contained in:
Julien Viet
2019-08-29 16:32:19 +02:00
parent 46a54e50ae
commit ec60b424ad
15 changed files with 470 additions and 77 deletions

20
pom.xml
View File

@@ -485,6 +485,26 @@
</includes>
</configuration>
</execution>
<execution>
<id>no-jackson</id>
<goals>
<goal>integration-test</goal>
</goals>
<phase>integration-test</phase>
<configuration>
<includes>
<include>io/vertx/it/JsonTest.java</include>
</includes>
<systemProperties>
<vertx-test-alpn-jdk>false</vertx-test-alpn-jdk>
<vertx-test-alpn-openssl>false</vertx-test-alpn-openssl>
</systemProperties>
<classpathDependencyExcludes>
<classpathDependencyExclude>com.fasterxml.jackson.core:jackson-core</classpathDependencyExclude>
<classpathDependencyExclude>com.fasterxml.jackson.core:jackson-databind</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</execution>
<!-- Check some netty compat (dns / http2) with unstable classes -->
<!-- Uncomment to run them, you need to have also the dependencies in the repo as they won't be downloaded automatically -->
<!-- It is possible to have them easily by running the build with -Dnetty.version=4.1.9.Final -Dtcnative.version=2.0.0.Final -->

View File

@@ -17,12 +17,12 @@ import io.vertx.codegen.annotations.Fluent;
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.JsonFactory;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
@@ -148,7 +148,7 @@ public interface Buffer extends ClusterSerializable, Shareable {
* @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 JsonFactory.factory.fromBuffer(this, Object.class);
}
/**

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -11,26 +11,18 @@
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.module.SimpleModule;
import io.netty.buffer.ByteBufInputStream;
import io.vertx.core.buffer.Buffer;
import java.io.IOException;
import java.io.InputStream;
import java.time.DateTimeException;
import java.time.Instant;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
/**
* @author <a href="http://tfox.org">Tim Fox</a>
*/
@@ -40,6 +32,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);
@@ -172,7 +168,7 @@ public class Json {
*/
public static Object decodeValue(Buffer buf) throws DecodeException {
try {
Object value = mapper.readValue((InputStream) new ByteBufInputStream(buf.getByteBuf()), Object.class);
Object value = decodeValue(buf, Object.class);
if (value instanceof List) {
List list = (List) value;
return new JsonArray(list);
@@ -218,58 +214,4 @@ public class Json {
throw new DecodeException("Failed to decode:" + e.getMessage(), e);
}
}
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);
}
}
}
}

View File

@@ -14,6 +14,7 @@ package io.vertx.core.json;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.shareddata.Shareable;
import io.vertx.core.shareddata.impl.ClusterSerializable;
import io.vertx.core.spi.JsonFactory;
import java.time.Instant;
import java.util.*;
@@ -550,7 +551,7 @@ public class JsonArray implements Iterable<Object>, ClusterSerializable, Shareab
* @return the string encoding
*/
public String encode() {
return Json.encode(list);
return JsonFactory.factory.toString(list, false);
}
/**
@@ -559,7 +560,7 @@ public class JsonArray implements Iterable<Object>, ClusterSerializable, Shareab
* @return the buffer encoding.
*/
public Buffer toBuffer() {
return Json.encodeToBuffer(list);
return JsonFactory.factory.toBuffer(list, false);
}
/**
@@ -568,7 +569,7 @@ public class JsonArray implements Iterable<Object>, ClusterSerializable, Shareab
* @return the string encoding
*/
public String encodePrettily() {
return Json.encodePrettily(list);
return JsonFactory.factory.toString(list, true);
}
/**
@@ -657,11 +658,11 @@ public class JsonArray implements Iterable<Object>, ClusterSerializable, Shareab
}
private void fromJson(String json) {
list = Json.decodeValue(json, List.class);
list = JsonFactory.factory.fromString(json, List.class);
}
private void fromBuffer(Buffer buf) {
list = Json.decodeValue(buf, List.class);
list = JsonFactory.factory.fromBuffer(buf, List.class);
}
private class Iter implements Iterator<Object> {

View File

@@ -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());
}
}

View File

@@ -14,6 +14,7 @@ import io.vertx.codegen.annotations.Fluent;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.shareddata.Shareable;
import io.vertx.core.shareddata.impl.ClusterSerializable;
import io.vertx.core.spi.JsonFactory;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
@@ -106,7 +107,7 @@ public class JsonObject implements Iterable<Map.Entry<String, Object>>, ClusterS
if (obj == null) {
return null;
} else {
return new JsonObject((Map<String, Object>) Json.mapper.convertValue(obj, Map.class));
return new JsonObject((Map<String, Object>) JsonFactory.factory.fromValue(obj, Map.class));
}
}
@@ -120,7 +121,7 @@ public class JsonObject implements Iterable<Map.Entry<String, Object>>, ClusterS
* if the type cannot be instantiated.
*/
public <T> T mapTo(Class<T> type) {
return Json.mapper.convertValue(map, type);
return JsonFactory.factory.fromValue(map, type);
}
/**
@@ -781,7 +782,7 @@ public class JsonObject implements Iterable<Map.Entry<String, Object>>, ClusterS
* @return the string encoding.
*/
public String encode() {
return Json.encode(map);
return JsonFactory.factory.toString(map, false);
}
/**
@@ -791,7 +792,7 @@ public class JsonObject implements Iterable<Map.Entry<String, Object>>, ClusterS
* @return the pretty string encoding.
*/
public String encodePrettily() {
return Json.encodePrettily(map);
return JsonFactory.factory.toString(map, true);
}
/**
@@ -800,7 +801,7 @@ public class JsonObject implements Iterable<Map.Entry<String, Object>>, ClusterS
* @return the buffer encoding.
*/
public Buffer toBuffer() {
return Json.encodeToBuffer(map);
return JsonFactory.factory.toBuffer(map, false);
}
/**
@@ -970,11 +971,11 @@ public class JsonObject implements Iterable<Map.Entry<String, Object>>, ClusterS
}
private void fromJson(String json) {
map = Json.decodeValue(json, Map.class);
map = JsonFactory.factory.fromString(json, Map.class);
}
private void fromBuffer(Buffer buf) {
map = Json.decodeValue(buf, Map.class);
map = JsonFactory.factory.fromBuffer(buf, Map.class);
}
private class Iter implements Iterator<Map.Entry<String, Object>> {

View File

@@ -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());
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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 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.JsonFactory;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
public class JsonFactoryImpl implements JsonFactory {
@Override
public <T> T fromValue(Object json, Class<T> clazz) {
T value = Json.mapper.convertValue(json, clazz);
if (clazz == Object.class) {
value = clazz.cast(adapt(value));
}
return value;
}
@Override
public <T> T fromString(String str, Class<T> clazz) throws DecodeException {
T value = Json.decodeValue(str, clazz);
if (clazz == Object.class) {
value = clazz.cast(adapt(value));
}
return value;
}
@Override
public <T> T fromBuffer(Buffer json, Class<T> clazz) throws DecodeException {
T value = Json.decodeValue(json, clazz);
if (clazz == Object.class) {
value = clazz.cast(adapt(value));
}
return value;
}
@Override
public String toString(Object object, boolean pretty) throws EncodeException {
if (pretty) {
return Json.encodePrettily(object);
} else {
return Json.encode(object);
}
}
private 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 Buffer toBuffer(Object object, boolean pretty) throws EncodeException {
if (pretty) {
return Buffer.buffer(toString(object, pretty));
} else {
return Json.encodeToBuffer(object);
}
}
}

View File

@@ -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;
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 JsonFactory {
JsonFactory factory = ServiceHelper.loadFactory(JsonFactory.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);
}
}

View File

@@ -0,0 +1 @@
io.vertx.core.json.impl.JsonFactoryImpl

View File

@@ -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();
}
}
}