diff --git a/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatch.java b/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatch.java index c9711f4f..0bce5d33 100644 --- a/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatch.java +++ b/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatch.java @@ -65,9 +65,8 @@ @JsonDeserialize(using = JsonMergePatchDeserializer.class) public abstract class JsonMergePatch implements JsonSerializable, Patch { - protected static final MessageBundle BUNDLE - = MessageBundles.getBundle(JsonPatchMessages.class); - private static final ObjectMapper MAPPER = JacksonUtils.newMapper(); + protected static final MessageBundle BUNDLE = MessageBundles.getBundle(JsonPatchMessages.class); + private static final ObjectMapper DEFAULT_MAPPER = JacksonUtils.newMapper(); /** * Build an instance from a JSON input @@ -77,11 +76,25 @@ public abstract class JsonMergePatch implements JsonSerializable, Patch { * @throws JsonPatchException failed to deserialize * @throws NullPointerException node is null */ - public static JsonMergePatch fromJson(final JsonNode node) - throws JsonPatchException { + public static JsonMergePatch fromJson(final JsonNode node) throws JsonPatchException { + return fromJson(node, DEFAULT_MAPPER); + } + + /** + * Build an instance from a JSON input with a custom ObjectMapper. + * This allows to customize the mapper used in deserialization of nodes. + * + * @param node the input + * @param mapper custom ObjectMapper + * @return a JSON Merge Patch instance + * @throws JsonPatchException failed to deserialize + * @throws NullPointerException node or mapper is null + */ + public static JsonMergePatch fromJson(final JsonNode node, ObjectMapper mapper) throws JsonPatchException { BUNDLE.checkNotNull(node, "jsonPatch.nullInput"); + BUNDLE.checkNotNull(mapper, "jsonPatch.nullInput"); try { - return MAPPER.readValue(node.traverse(), JsonMergePatch.class); + return mapper.readValue(node.traverse(), JsonMergePatch.class); } catch (IOException e) { throw new JsonPatchException( BUNDLE.getMessage("jsonPatch.deserFailed"), e); diff --git a/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatchDeserializer.java b/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatchDeserializer.java index 8ccca010..cf15a045 100644 --- a/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatchDeserializer.java +++ b/src/main/java/com/gravity9/jsonpatch/mergepatch/JsonMergePatchDeserializer.java @@ -20,13 +20,11 @@ package com.gravity9.jsonpatch.mergepatch; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.NullNode; -import com.github.fge.jackson.JacksonUtils; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; @@ -35,30 +33,20 @@ import java.util.Set; final class JsonMergePatchDeserializer extends JsonDeserializer { - /* - * FIXME! UGLY! HACK! - * - * We MUST have an ObjectCodec ready so that the parser in .deserialize() - * can actually do something useful -- for instance, deserializing even a - * JsonNode. - * - * Jackson does not do this automatically; I don't know why... - */ - private static final ObjectCodec CODEC = JacksonUtils.newMapper(); @Override public JsonMergePatch deserialize(final JsonParser jp, - final DeserializationContext ctxt) - throws IOException, JsonProcessingException { - // FIXME: see comment above - jp.setCodec(CODEC); + final DeserializationContext ctxt) throws IOException { + ObjectMapper mapper = new ObjectMapper().setConfig(ctxt.getConfig()); + jp.setCodec(mapper); final JsonNode node = jp.readValueAsTree(); /* * Not an object: the simple case */ - if (!node.isObject()) + if (!node.isObject()) { return new NonObjectMergePatch(node); + } /* * The complicated case... @@ -67,8 +55,8 @@ public JsonMergePatch deserialize(final JsonParser jp, * members. */ - final Set removedMembers = new HashSet(); - final Map modifiedMembers = new HashMap(); + final Set removedMembers = new HashSet<>(); + final Map modifiedMembers = new HashMap<>(); final Iterator> iterator = node.fields(); Map.Entry entry; diff --git a/src/main/java/com/gravity9/jsonpatch/mergepatch/NonObjectMergePatch.java b/src/main/java/com/gravity9/jsonpatch/mergepatch/NonObjectMergePatch.java index 07aefa07..1f945c99 100644 --- a/src/main/java/com/gravity9/jsonpatch/mergepatch/NonObjectMergePatch.java +++ b/src/main/java/com/gravity9/jsonpatch/mergepatch/NonObjectMergePatch.java @@ -20,11 +20,9 @@ package com.gravity9.jsonpatch.mergepatch; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.gravity9.jsonpatch.JsonPatchException; import java.io.IOException; import javax.annotation.ParametersAreNonnullByDefault; @@ -41,23 +39,21 @@ final class NonObjectMergePatch extends JsonMergePatch { } @Override - public JsonNode apply(final JsonNode input) - throws JsonPatchException { + public JsonNode apply(final JsonNode input) { BUNDLE.checkNotNull(input, "jsonPatch.nullValue"); return node; } @Override public void serialize(final JsonGenerator jgen, - final SerializerProvider provider) - throws IOException, JsonProcessingException { + final SerializerProvider provider) throws IOException { jgen.writeTree(node); } @Override public void serializeWithType(final JsonGenerator jgen, - final SerializerProvider provider, final TypeSerializer typeSer) - throws IOException, JsonProcessingException { + final SerializerProvider provider, + final TypeSerializer typeSer) throws IOException { serialize(jgen, provider); } } diff --git a/src/main/java/com/gravity9/jsonpatch/mergepatch/ObjectMergePatch.java b/src/main/java/com/gravity9/jsonpatch/mergepatch/ObjectMergePatch.java index 62bdfd3c..a42226ce 100644 --- a/src/main/java/com/gravity9/jsonpatch/mergepatch/ObjectMergePatch.java +++ b/src/main/java/com/gravity9/jsonpatch/mergepatch/ObjectMergePatch.java @@ -20,7 +20,6 @@ package com.gravity9.jsonpatch.mergepatch; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; @@ -44,13 +43,12 @@ final class ObjectMergePatch extends JsonMergePatch { ObjectMergePatch(final Set removedMembers, final Map modifiedMembers) { - this.removedMembers = Collections.unmodifiableSet(new HashSet(removedMembers)); - this.modifiedMembers = Collections.unmodifiableMap(new HashMap(modifiedMembers)); + this.removedMembers = Collections.unmodifiableSet(new HashSet<>(removedMembers)); + this.modifiedMembers = Collections.unmodifiableMap(new HashMap<>(modifiedMembers)); } @Override - public JsonNode apply(final JsonNode input) - throws JsonPatchException { + public JsonNode apply(final JsonNode input) throws JsonPatchException { BUNDLE.checkNotNull(input, "jsonPatch.nullValue"); /* * If the input is an object, we make a deep copy of it @@ -90,8 +88,7 @@ public JsonNode apply(final JsonNode input) @Override public void serialize(final JsonGenerator jgen, - final SerializerProvider provider) - throws IOException, JsonProcessingException { + final SerializerProvider provider) throws IOException { jgen.writeStartObject(); /* @@ -114,8 +111,8 @@ public void serialize(final JsonGenerator jgen, @Override public void serializeWithType(final JsonGenerator jgen, - final SerializerProvider provider, final TypeSerializer typeSer) - throws IOException, JsonProcessingException { + final SerializerProvider provider, + final TypeSerializer typeSer) throws IOException { serialize(jgen, provider); } } diff --git a/src/test/java/com/gravity9/jsonpatch/mergepatch/SerializationTest.java b/src/test/java/com/gravity9/jsonpatch/mergepatch/SerializationTest.java index e8ae4f25..82397dd1 100644 --- a/src/test/java/com/gravity9/jsonpatch/mergepatch/SerializationTest.java +++ b/src/test/java/com/gravity9/jsonpatch/mergepatch/SerializationTest.java @@ -19,8 +19,10 @@ package com.gravity9.jsonpatch.mergepatch; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.github.fge.jackson.JacksonUtils; import com.github.fge.jackson.JsonLoader; import com.github.fge.jackson.JsonNumEquals; @@ -34,6 +36,7 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; +import static org.testng.AssertJUnit.assertEquals; public final class SerializationTest { @@ -102,4 +105,17 @@ public void objectSerDeserWorksCorrectly(final JsonNode input) assertTrue(EQUIVALENCE.equivalent(input, serialized)); } + + @Test + void testDecimalsKeptAfterPatchWithCustomObjectMapper() throws Exception { + ObjectMapper mapper = new ObjectMapper() + .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) + .setNodeFactory(new JsonNodeFactory(true)); + + JsonNode newer = mapper.readTree("25.000"); + JsonNode older = mapper.readTree("24.444"); + JsonNode apply = JsonMergePatch.fromJson(newer, mapper).apply(older); + + assertEquals("25.000", apply.asText()); + } }