diff --git a/README.md b/README.md index 629a1f61..34d7f942 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,9 @@ public class Example { } ``` +For the lighter weight Jackson Jr, use `JacksonJrEncoder` and `JacksonJrDecoder` from +the [Jackson Jr Module](./jackson-jr). + ### Sax [SaxDecoder](./sax) allows you to decode XML in a way that is compatible with normal JVM and also Android environments. diff --git a/jackson-jr/README.md b/jackson-jr/README.md new file mode 100644 index 00000000..fcc58657 --- /dev/null +++ b/jackson-jr/README.md @@ -0,0 +1,43 @@ +Jackson Jr Codec +=================== + +This module adds support for encoding and decoding JSON via Jackson Jr. This +is a significantly smaller and faster version of Jackson, which may be of benefit +in smaller runtime containers. + +Add `JacksonJrEncoder` and/or `JacksonJrDecoder` to your `Feign.Builder` like so: + +```java +GitHub github = Feign.builder() + .encoder(new JacksonJrEncoder()) + .decoder(new JacksonJrDecoder()) + .target(GitHub.class, "https://api.github.com"); +``` + +If you want to customize the `JSON` object that is used, provide it to the `JacksonJrEncoder` and `JacksonJrDecoder`: + +```java +JSON json = Json.builder() + .register(JacksonAnnotationExtension.builder() + .withVisibility(JsonAutoDetect.Value.defaultVisibility()) + .build()) + .build(); + +GitHub github = Feign.builder() + .encoder(new JacksonJrEncoder(json)) + .decoder(new JacksonJrDecoder(json)) + .target(GitHub.class, "https://api.github.com"); +``` + +Customisation is also possible by passing `JacksonJrExtension` objects +into the constructor of the `JacksonJrEncoder` or `JacksonJrDecoder`: + +```java +List extensions = singletonList(JacksonAnnotationExtension.builder() + .withVisibility(JsonAutoDetect.Value.defaultVisibility()) + .build()); +GitHub github = Feign.builder() + .encoder(new JacksonJrEncoder(extensions)) + .decoder(new JacksonJrDecoder(extensions)) + .target(GitHub.class, "https://api.github.com"); +``` diff --git a/jackson-jr/pom.xml b/jackson-jr/pom.xml new file mode 100644 index 00000000..9935213b --- /dev/null +++ b/jackson-jr/pom.xml @@ -0,0 +1,58 @@ + + + + 4.0.0 + + + io.github.openfeign + parent + 11.3-SNAPSHOT + + + feign-jackson-jr + Feign Jackson Jr + Feign Jackson Jr + + + ${project.basedir}/.. + + + + + ${project.groupId} + feign-core + + + + com.fasterxml.jackson.jr + jackson-jr-objects + + + + com.fasterxml.jackson.jr + jackson-jr-annotation-support + test + + + + ${project.groupId} + feign-core + test-jar + test + + + diff --git a/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrDecoder.java b/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrDecoder.java new file mode 100644 index 00000000..bb0f3e99 --- /dev/null +++ b/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrDecoder.java @@ -0,0 +1,107 @@ +/** + * Copyright 2012-2021 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.jackson.jr; + +import com.fasterxml.jackson.jr.ob.JSON; +import com.fasterxml.jackson.jr.ob.JSONObjectException; +import com.fasterxml.jackson.jr.ob.JacksonJrExtension; +import feign.Response; +import feign.codec.DecodeException; +import feign.codec.Decoder; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * A {@link Decoder} that uses Jackson Jr to convert objects to String or byte representation. + */ +public class JacksonJrDecoder extends JacksonJrMapper implements Decoder { + + @FunctionalInterface + interface Transformer { + Object apply(JSON mapper, Reader reader) throws IOException; + } + + public JacksonJrDecoder() { + super(); + } + + /** + * Construct with a custom {@link JSON} to use for decoding + * + * @param mapper the mapper to use + */ + public JacksonJrDecoder(JSON mapper) { + super(mapper); + } + + /** + * Construct with a series of {@link JacksonJrExtension} objects that are registered into the + * {@link JSON} + * + * @param iterable the source of the extensions + */ + public JacksonJrDecoder(Iterable iterable) { + super(iterable); + } + + @Override + public Object decode(Response response, Type type) throws IOException { + + Transformer transformer = findTransformer(response, type); + + if (response.body() == null) { + return null; + } + Reader reader = response.body().asReader(response.charset()); + if (!reader.markSupported()) { + reader = new BufferedReader(reader, 1); + } + try { + // Read the first byte to see if we have any data + reader.mark(1); + if (reader.read() == -1) { + return null; // Eagerly returning null avoids "No content to map due to end-of-input" + } + reader.reset(); + return transformer.apply(mapper, reader); + } catch (JSONObjectException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + throw e; + } + } + + private static Transformer findTransformer(Response response, Type type) { + if (type instanceof Class) { + return (mapper, reader) -> mapper.beanFrom((Class) type, reader); + } + if (type instanceof ParameterizedType) { + Type rawType = ((ParameterizedType) type).getRawType(); + Type[] parameterType = ((ParameterizedType) type).getActualTypeArguments(); + if (rawType.equals(List.class)) { + return (mapper, reader) -> mapper.listOfFrom((Class) parameterType[0], reader); + } + if (rawType.equals(Map.class)) { + return (mapper, reader) -> mapper.mapOfFrom((Class) parameterType[1], reader); + } + } + throw new DecodeException(500, "Cannot decode type: " + type.getTypeName(), response.request()); + } +} diff --git a/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrEncoder.java b/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrEncoder.java new file mode 100644 index 00000000..09b76f9d --- /dev/null +++ b/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrEncoder.java @@ -0,0 +1,65 @@ +/** + * Copyright 2012-2021 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.jackson.jr; + +import java.io.IOException; +import java.lang.reflect.Type; +import com.fasterxml.jackson.jr.ob.JSON; +import com.fasterxml.jackson.jr.ob.JacksonJrExtension; +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; +import static java.lang.String.format; + +/** + * A {@link Encoder} that uses Jackson Jr to convert objects to String or byte representation. + */ +public class JacksonJrEncoder extends JacksonJrMapper implements Encoder { + + public JacksonJrEncoder() { + super(); + } + + /** + * Construct with a custom {@link JSON} to use for encoding + * + * @param mapper the mapper to use + */ + public JacksonJrEncoder(JSON mapper) { + super(mapper); + } + + /** + * Construct with a series of {@link JacksonJrExtension} objects that are registered into the + * {@link JSON} + * + * @param iterable the source of the extensions + */ + public JacksonJrEncoder(Iterable iterable) { + super(iterable); + } + + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) { + try { + if (bodyType == byte[].class) { + template.body(mapper.asBytes(object), null); + } else { + template.body(mapper.asString(object)); + } + } catch (IOException e) { + throw new EncodeException(e.getMessage(), e); + } + } +} diff --git a/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrMapper.java b/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrMapper.java new file mode 100644 index 00000000..38d39c6c --- /dev/null +++ b/jackson-jr/src/main/java/feign/jackson/jr/JacksonJrMapper.java @@ -0,0 +1,49 @@ +/** + * Copyright 2012-2021 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.jackson.jr; + +import com.fasterxml.jackson.jr.ob.JSON; +import com.fasterxml.jackson.jr.ob.JacksonJrExtension; + +/** + * Common implementation of the holder of a {@link JSON}, DRYing out construction. + */ +abstract class JacksonJrMapper { + protected final JSON mapper; + + protected JacksonJrMapper() { + this(JSON.std); + } + + /** + * Construct with a custom {@link JSON} to use for decoding/encoding + * + * @param mapper the mapper to use + */ + protected JacksonJrMapper(JSON mapper) { + this.mapper = mapper; + } + + /** + * Construct with a series of {@link JacksonJrExtension} objects that are registered into the + * {@link JSON} + * + * @param iterable the source of the extensions + */ + protected JacksonJrMapper(Iterable iterable) { + JSON.Builder builder = JSON.builder(); + iterable.forEach(builder::register); + this.mapper = builder.build(); + } +} diff --git a/jackson-jr/src/test/java/feign/jackson/jr/JacksonCodecTest.java b/jackson-jr/src/test/java/feign/jackson/jr/JacksonCodecTest.java new file mode 100644 index 00000000..19a6cf2f --- /dev/null +++ b/jackson-jr/src/test/java/feign/jackson/jr/JacksonCodecTest.java @@ -0,0 +1,285 @@ +/** + * Copyright 2012-2021 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.jackson.jr; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.jr.ob.JSON; +import feign.Request; +import feign.Request.HttpMethod; +import feign.RequestTemplate; +import feign.Response; +import feign.Util; +import org.junit.Test; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.util.*; +import static feign.Util.UTF_8; +import static feign.assertj.FeignAssertions.assertThat; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class JacksonCodecTest { + + private static final String DATES_JSON = "[\"2020-01-02\",\"2021-02-03\"]"; + + @Test + public void encodesMapObjectNumericalValuesAsInteger() { + Map map = new LinkedHashMap<>(); + map.put("foo", 1); + + RequestTemplate template = new RequestTemplate(); + new JacksonJrEncoder().encode(map, map.getClass(), template); + + assertThat(template).hasBody("{\"foo\":1}"); + } + + @Test + public void encodesMapObjectNumericalValuesToByteArray() { + Map map = new LinkedHashMap<>(); + map.put("foo", 1); + + RequestTemplate template = new RequestTemplate(); + new JacksonJrEncoder().encode(map, byte[].class, template); + + assertThat(template).hasBody("{\"foo\":1}"); + } + + @Test + public void encodesFormParams() { + Map form = new LinkedHashMap(); + form.put("foo", 1); + form.put("bar", Arrays.asList(2, 3)); + + RequestTemplate template = new RequestTemplate(); + new JacksonJrEncoder().encode(form, new TypeReference>() {}.getType(), template); + + assertThat(template).hasBody("{\"foo\":1,\"bar\":[2,3]}"); + } + + @Test + public void decodes() throws Exception { + List zones = new LinkedList<>(); + zones.add(new Zone("denominator.io.")); + zones.add(new Zone("denominator.io.", "ABCD")); + + String zonesJson = + "[{\"name\":\"denominator.io.\"},{\"name\":\"denominator.io.\",\"id\":\"ABCD\"}]"; + + Response response = Response.builder() + .status(200) + .reason("OK") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(Collections.emptyMap()) + .body(zonesJson, UTF_8) + .build(); + assertEquals(zones, + new JacksonJrDecoder().decode(response, new TypeReference>() {}.getType())); + } + + @Test + public void nullBodyDecodesToNull() throws Exception { + Response response = Response.builder() + .status(204) + .reason("OK") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(Collections.emptyMap()) + .build(); + assertNull(new JacksonJrDecoder().decode(response, String.class)); + } + + @Test + public void emptyBodyDecodesToNull() throws Exception { + Response response = Response.builder() + .status(204) + .reason("OK") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(Collections.emptyMap()) + .body(new byte[0]) + .build(); + assertNull(new JacksonJrDecoder().decode(response, String.class)); + } + + @Test + public void customDecoder() throws Exception { + JacksonJrDecoder decoder = new JacksonJrDecoder( + singletonList(new JavaLocalDateExtension())); + + List dates = new LinkedList<>(); + dates.add(LocalDate.of(2020, 1, 2)); + dates.add(LocalDate.of(2021, 2, 3)); + + Response response = Response.builder() + .status(200) + .reason("OK") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(Collections.emptyMap()) + .body(DATES_JSON, UTF_8) + .build(); + assertEquals(dates, + decoder.decode(response, new TypeReference>() {}.getType())); + } + + @Test + public void customDecoderExpressedAsMapper() throws Exception { + JSON mapper = JSON.builder() + .register(new JavaLocalDateExtension()) + .build(); + JacksonJrDecoder decoder = new JacksonJrDecoder(mapper); + + List dates = new LinkedList<>(); + dates.add(LocalDate.of(2020, 1, 2)); + dates.add(LocalDate.of(2021, 2, 3)); + + Response response = Response.builder() + .status(200) + .reason("OK") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(Collections.emptyMap()) + .body(DATES_JSON, UTF_8) + .build(); + assertEquals(dates, + decoder.decode(response, new TypeReference>() {}.getType())); + } + + @Test + public void customEncoder() { + JacksonJrEncoder encoder = new JacksonJrEncoder( + singletonList(new JavaLocalDateExtension())); + + List dates = new LinkedList<>(); + dates.add(LocalDate.of(2020, 1, 2)); + dates.add(LocalDate.of(2021, 2, 3)); + + RequestTemplate template = new RequestTemplate(); + encoder.encode(dates, new TypeReference>() {}.getType(), template); + + assertThat(template).hasBody(DATES_JSON); + } + + @Test + public void decoderCharset() throws IOException { + Zone zone = new Zone("denominator.io.", "ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÑ"); + + Map> headers = new HashMap>(); + headers.put("Content-Type", Arrays.asList("application/json;charset=ISO-8859-1")); + + Response response = Response.builder() + .status(200) + .reason("OK") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(headers) + .body(new String("" // + + "{" + System.lineSeparator() + + " \"name\" : \"DENOMINATOR.IO.\"," + System.lineSeparator() + + " \"id\" : \"ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÑ\"" + System.lineSeparator() + + "}").getBytes(StandardCharsets.ISO_8859_1)) + .build(); + assertEquals(zone.getId(), + ((Zone) new JacksonJrDecoder().decode(response, Zone.class)) + .getId()); + } + + @Test + public void decodesToMap() throws Exception { + String json = "{\"name\":\"jim\",\"id\":12}"; + + Response response = Response.builder() + .status(200) + .reason("OK") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(Collections.emptyMap()) + .body(json, UTF_8) + .build(); + + Map map = (Map) new JacksonJrDecoder() + .decode(response, new TypeReference>() {}.getType()); + + assertEquals(12, map.get("id")); + assertEquals("jim", map.get("name")); + } + + public static class Zone { + + private String name; + private String id; + + public Zone() { + // for reflective instantiation. + } + + public Zone(String name) { + this.name = name; + } + + public Zone(String name, String id) { + this.name = name; + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Zone zone = (Zone) o; + return Objects.equals(name, zone.name) && Objects.equals(id, zone.id); + } + + @Override + public int hashCode() { + return Objects.hash(name, id); + } + + @Override + public String toString() { + return "Zone{" + + "name='" + name + '\'' + + ", id='" + id + '\'' + + '}'; + } + } + + /** Enabled via {@link feign.Feign.Builder#decode404()} */ + @Test + public void notFoundDecodesToNull() throws Exception { + Response response = Response.builder() + .status(404) + .reason("NOT FOUND") + .request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) + .headers(Collections.emptyMap()) + .build(); + assertThat((byte[]) new JacksonJrDecoder().decode(response, byte[].class)).isNull(); + } +} diff --git a/jackson-jr/src/test/java/feign/jackson/jr/JavaLocalDateExtension.java b/jackson-jr/src/test/java/feign/jackson/jr/JavaLocalDateExtension.java new file mode 100644 index 00000000..3104b5e3 --- /dev/null +++ b/jackson-jr/src/test/java/feign/jackson/jr/JavaLocalDateExtension.java @@ -0,0 +1,74 @@ +/** + * Copyright 2012-2021 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.jackson.jr; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.jr.ob.JacksonJrExtension; +import com.fasterxml.jackson.jr.ob.api.ExtensionContext; +import com.fasterxml.jackson.jr.ob.api.ReaderWriterProvider; +import com.fasterxml.jackson.jr.ob.api.ValueReader; +import com.fasterxml.jackson.jr.ob.api.ValueWriter; +import com.fasterxml.jackson.jr.ob.impl.JSONReader; +import com.fasterxml.jackson.jr.ob.impl.JSONWriter; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Adapted from https://www.andersaaberg.dk/2020/enable-support-for-java-time-with-jackson-jr/ This + * adds Java Time support to Jackson JR + */ +public class JavaLocalDateExtension extends JacksonJrExtension { + private static class LocalDateReaderWriterProvider extends ReaderWriterProvider { + @Override + public ValueReader findValueReader(JSONReader readContext, Class type) { + return type == LocalDate.class ? new LocalDateValueReader() : null; + } + + @Override + public ValueWriter findValueWriter(JSONWriter writeContext, Class type) { + return type == LocalDate.class ? new LocalDateValueWriter() : null; + } + } + + private static class LocalDateValueReader extends ValueReader { + protected LocalDateValueReader() { + super(LocalDate.class); + } + + @Override + public Object read(JSONReader reader, JsonParser p) throws IOException { + return LocalDate.parse(p.getText(), DateTimeFormatter.ISO_LOCAL_DATE); + } + } + + private static class LocalDateValueWriter implements ValueWriter { + @Override + public void writeValue(JSONWriter context, JsonGenerator g, Object value) throws IOException { + context.writeValue(((LocalDate) value).format(DateTimeFormatter.ISO_LOCAL_DATE)); + } + + @Override + public Class valueType() { + return LocalDate.class; + } + } + + @Override + protected void register(ExtensionContext ctxt) { + ctxt.insertProvider(new LocalDateReaderWriterProvider()); + } +} + diff --git a/jackson-jr/src/test/java/feign/jackson/jr/examples/GitHubExample.java b/jackson-jr/src/test/java/feign/jackson/jr/examples/GitHubExample.java new file mode 100644 index 00000000..2eb4df5b --- /dev/null +++ b/jackson-jr/src/test/java/feign/jackson/jr/examples/GitHubExample.java @@ -0,0 +1,58 @@ +/** + * Copyright 2012-2021 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.jackson.jr.examples; + +import java.util.List; +import feign.Feign; +import feign.Param; +import feign.RequestLine; +import feign.jackson.jr.JacksonJrDecoder; + +/** + * adapted from {@code com.example.retrofit.GitHubClient} + */ +public class GitHubExample { + + public static void main(String... args) { + GitHub github = Feign.builder() + .decoder(new JacksonJrDecoder()) + .target(GitHub.class, "https://api.github.com"); + + System.out.println("Let's fetch and print a list of the contributors to this library."); + List contributors = github.contributors("netflix", "feign"); + for (Contributor contributor : contributors) { + System.out.println(contributor.login + " (" + contributor.contributions + ")"); + } + } + + interface GitHub { + + @RequestLine("GET /repos/{owner}/{repo}/contributors") + List contributors(@Param("owner") String owner, @Param("repo") String repo); + } + + static class Contributor { + + private String login; + private int contributions; + + void setLogin(String login) { + this.login = login; + } + + void setContributions(int contributions) { + this.contributions = contributions; + } + } +} diff --git a/pom.xml b/pom.xml index f38b0c9d..dd430b51 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ hystrix jackson jackson-jaxb + jackson-jr jaxb jaxrs jaxrs2 @@ -336,6 +337,18 @@ ${jackson.version} + + com.fasterxml.jackson.jr + jackson-jr-objects + ${jackson.version} + + + + com.fasterxml.jackson.jr + jackson-jr-annotation-support + ${jackson.version} + + org.slf4j slf4j-simple diff --git a/src/docs/overview-mindmap.iuml b/src/docs/overview-mindmap.iuml index 5d7e1135..1e647e84 100644 --- a/src/docs/overview-mindmap.iuml +++ b/src/docs/overview-mindmap.iuml @@ -23,6 +23,7 @@ *** Jackson 1 *** Jackson 2 *** Jackson JAXB +*** Jackson Jr *** Sax ** metrics *** Dropwizard Metrics 5