From d876b698e902cc5de566aa1e39ce8b75acbcee11 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 30 May 2024 14:38:26 +0200 Subject: [PATCH] GH-1619: fix Precision loss with SenMLJSON and Old JSON content Format. --- .../LwM2mJsonJacksonEncoderDecoder.java | 20 +++++- .../SenMLJsonJacksonEncoderDecoder.java | 15 +++- .../core/json/JsonDeserializerTest.java | 2 +- .../codec/LwM2mNodeDecoderEncoderTest.java | 68 +++++++++++++++++++ 4 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderEncoderTest.java diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/json/jackson/LwM2mJsonJacksonEncoderDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/core/json/jackson/LwM2mJsonJacksonEncoderDecoder.java index 1a25cab88f..c376e90ff7 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/json/jackson/LwM2mJsonJacksonEncoderDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/json/jackson/LwM2mJsonJacksonEncoderDecoder.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.core.json.LwM2mJsonException; import org.eclipse.leshan.core.util.json.JsonException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; @@ -33,9 +34,22 @@ */ public class LwM2mJsonJacksonEncoderDecoder implements LwM2mJsonDecoder, LwM2mJsonEncoder { - private static final JsonRootObjectSerDes serDes = new JsonRootObjectSerDes(); - private static final ObjectMapper mapper = new ObjectMapper() - .configure(JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES, false); + private final JsonRootObjectSerDes serDes; + private final ObjectMapper mapper; + + public LwM2mJsonJacksonEncoderDecoder() { + this(new JsonRootObjectSerDes(), null); + } + + public LwM2mJsonJacksonEncoderDecoder(JsonRootObjectSerDes serDes, ObjectMapper objectMapper) { + this.serDes = serDes; + this.mapper = objectMapper == null ? createDefaultObjectMapper() : objectMapper; + } + + protected ObjectMapper createDefaultObjectMapper() { + return new ObjectMapper().configure(JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES, false) + .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + } @Override public String toJsonLwM2m(JsonRootObject jro) throws LwM2mJsonException { diff --git a/leshan-core/src/main/java/org/eclipse/leshan/senml/json/jackson/SenMLJsonJacksonEncoderDecoder.java b/leshan-core/src/main/java/org/eclipse/leshan/senml/json/jackson/SenMLJsonJacksonEncoderDecoder.java index 93a37da277..109000b3fa 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/senml/json/jackson/SenMLJsonJacksonEncoderDecoder.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/senml/json/jackson/SenMLJsonJacksonEncoderDecoder.java @@ -32,6 +32,7 @@ import org.eclipse.leshan.senml.SenMLPack; import org.eclipse.leshan.senml.SenMLRecord; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; @@ -41,8 +42,7 @@ */ public class SenMLJsonJacksonEncoderDecoder implements SenMLDecoder, SenMLEncoder { private final JacksonJsonSerDes serDes; - private static final ObjectMapper mapper = new ObjectMapper() - .configure(JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES, false); + private final ObjectMapper mapper; public SenMLJsonJacksonEncoderDecoder() { this(false); @@ -68,7 +68,18 @@ public SenMLJsonJacksonEncoderDecoder(boolean allowNoValue, Base64Decoder base64 } public SenMLJsonJacksonEncoderDecoder(JacksonJsonSerDes senMLJSONSerializerDeserializer) { + this(senMLJSONSerializerDeserializer, null); + } + + public SenMLJsonJacksonEncoderDecoder(JacksonJsonSerDes senMLJSONSerializerDeserializer, + ObjectMapper objectMapper) { this.serDes = senMLJSONSerializerDeserializer; + this.mapper = objectMapper == null ? createDefaultObjectMapper() : objectMapper; + } + + protected ObjectMapper createDefaultObjectMapper() { + return new ObjectMapper().configure(JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES, false) + .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); } @Override diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/json/JsonDeserializerTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/json/JsonDeserializerTest.java index 06edbf2120..dfb976a67e 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/json/JsonDeserializerTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/json/JsonDeserializerTest.java @@ -47,7 +47,7 @@ public void deserialize_device_object() throws LwM2mJsonException { b.append("{\"n\":\"9\",\"v\":100},"); b.append("{\"n\":\"10\",\"v\":15},"); b.append("{\"n\":\"11/0\",\"v\":0},"); - b.append("{\"n\":\"13\",\"v\":1.367491215E9},"); + b.append("{\"n\":\"13\",\"v\":1367491215},"); b.append("{\"n\":\"14\",\"sv\":\"+02:00\"},"); b.append("{\"n\":\"15\",\"sv\":\"U\"}]}"); diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderEncoderTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderEncoderTest.java new file mode 100644 index 0000000000..af9fa886a4 --- /dev/null +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/codec/LwM2mNodeDecoderEncoderTest.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2024 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.core.node.codec; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.node.TimestampedLwM2mNode; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.util.TestObjectLoader; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class LwM2mNodeDecoderEncoderTest { + + private final LwM2mDecoder decoder = new DefaultLwM2mDecoder(); + private final LwM2mEncoder encoder = new DefaultLwM2mEncoder(); + private final LwM2mModel model = new StaticModel(TestObjectLoader.loadAllDefault()); + + static Stream contentFormats() { + return Stream.of(// + arguments(ContentFormat.JSON), // + arguments(ContentFormat.SENML_JSON), // + arguments(ContentFormat.SENML_CBOR)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("contentFormats") + public void encode_decode_timestamped_value(ContentFormat format) { + + // resource used for test + LwM2mPath resourcePath = new LwM2mPath("3442/0/120"); + Instant t1 = Instant.parse("2024-05-23T21:55:00.556635828Z"); + LwM2mSingleResource resource = LwM2mSingleResource.newIntegerResource(resourcePath.getResourceId(), 3600); + List timestampedData = Arrays.asList(new TimestampedLwM2mNode(t1, resource)); + + // try to encode then to decode and compare result + byte[] encodedTimestampedData = encoder.encodeTimestampedData(timestampedData, format, resourcePath, model); + + List decodeTimestampedData = decoder.decodeTimestampedData(encodedTimestampedData, format, + resourcePath, model); + + assertEquals(timestampedData, decodeTimestampedData); + } + +}