forked from eclipse-archived/smarthome
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix number deserialization on Json Storage
As it turned out, the current Map deserializer was not very effective in converting numbers to BigDecimals as it took itself out of the game when deserializing the outer map which JsonStorage adds itself. Hence a property map within an entity lost its normalization. This could have been compensated again during read by simply cutting off the trailing ".0"s, but this still led to the awkward situation that the numbers in the serialized format were altered and looked different of what would have been expected. Therefore this change * Distinguishes the "outer map" (i.e. the internal data structure of the JsonStorage and the inner entity structures * Uses two different Gson instances for handling those two structures individually * Lets JsonStorage internally keep JsonElement structures _always_ * Registers a custom deserializer for Configuration which handles the custom number-to-BigDecimal conversion fixes eclipse-archived#3774 Signed-off-by: Simon Kaufmann <[email protected]>
- Loading branch information
Simon Kaufmann
committed
Jul 5, 2017
1 parent
db3a098
commit cfcd58e
Showing
8 changed files
with
339 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
...e.storage.json.test/src/main/java/org/eclipse/smarthome/storage/json/JSonStorageTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* Copyright (c) 2014-2017 by the respective copyright holders. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
*/ | ||
package org.eclipse.smarthome.storage.json; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.math.BigDecimal; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
import org.apache.commons.io.FileUtils; | ||
import org.eclipse.smarthome.config.core.Configuration; | ||
import org.eclipse.smarthome.storage.json.JsonStorage; | ||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
/** | ||
* This test makes sure that the JSonStorage loads all stored numbers as BigDecimal | ||
* | ||
* @author Stefan Triller - Initial Contribution | ||
*/ | ||
public class JSonStorageTest { | ||
|
||
private JsonStorage<DummyObject> objectStorage; | ||
private File tmpFile; | ||
|
||
@Before | ||
public void setUp() throws IOException { | ||
tmpFile = File.createTempFile("storage-debug", ".json"); | ||
tmpFile.deleteOnExit(); | ||
|
||
objectStorage = new JsonStorage<>(tmpFile, this.getClass().getClassLoader(), 0, 0, 0); | ||
objectStorage.put("DummyObject", new DummyObject()); | ||
} | ||
|
||
private void persistAndReadAgain() { | ||
objectStorage.commitDatabase(); | ||
objectStorage = new JsonStorage<>(tmpFile, this.getClass().getClassLoader(), 0, 0, 0); | ||
} | ||
|
||
@Test | ||
public void allInsertedNumbersAreLoadedAsBigDecimal_fromCache() { | ||
DummyObject dummy = objectStorage.get("DummyObject"); | ||
|
||
Assert.assertTrue(dummy.configuration.get("testShort") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testInt") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testLong") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testDouble") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testFloat") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testBigDecimal") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testBoolean") instanceof Boolean); | ||
Assert.assertTrue(dummy.configuration.get("testString") instanceof String); | ||
} | ||
|
||
@Test | ||
public void allInsertedNumbersAreLoadedAsBigDecimal_fromDisk() { | ||
persistAndReadAgain(); | ||
DummyObject dummy = objectStorage.get("DummyObject"); | ||
|
||
Assert.assertTrue(dummy.configuration.get("testShort") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testInt") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testLong") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testDouble") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testFloat") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testBigDecimal") instanceof BigDecimal); | ||
Assert.assertTrue(dummy.configuration.get("testBoolean") instanceof Boolean); | ||
Assert.assertTrue(dummy.configuration.get("testString") instanceof String); | ||
} | ||
|
||
@Test | ||
public void testIntegerScale_fromCache() { | ||
objectStorage.put("DummyObject", new DummyObject()); | ||
DummyObject dummy = objectStorage.get("DummyObject"); | ||
|
||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testShort")).scale(), 0); | ||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testInt")).scale(), 0); | ||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testLong")).scale(), 0); | ||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testBigDecimal")).scale(), 0); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Test | ||
public void testIntegerScale_fromDisk() { | ||
objectStorage.put("DummyObject", new DummyObject()); | ||
DummyObject dummy = objectStorage.get("DummyObject"); | ||
|
||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testShort")).scale(), 0); | ||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testInt")).scale(), 0); | ||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testLong")).scale(), 0); | ||
Assert.assertEquals(((BigDecimal) dummy.configuration.get("testBigDecimal")).scale(), 0); | ||
Assert.assertEquals(((List<BigDecimal>) dummy.configuration.get("multiInt")).get(0).scale(), 0); | ||
Assert.assertEquals(((List<BigDecimal>) dummy.configuration.get("multiInt")).get(1).scale(), 0); | ||
Assert.assertEquals(((List<BigDecimal>) dummy.configuration.get("multiInt")).get(2).scale(), 0); | ||
Assert.assertEquals(((BigDecimal) dummy.channels.get(0).configuration.get("testChildLong")).scale(), 0); | ||
} | ||
|
||
@Test | ||
public void testStableOutput() throws IOException { | ||
persistAndReadAgain(); | ||
String storageString1 = FileUtils.readFileToString(tmpFile); | ||
|
||
objectStorage = new JsonStorage<>(tmpFile, this.getClass().getClassLoader(), 0, 0, 0); | ||
objectStorage.commitDatabase(); | ||
String storageString2 = FileUtils.readFileToString(tmpFile); | ||
|
||
assertEquals(storageString1, storageString2); | ||
} | ||
|
||
private static class DummyObject { | ||
|
||
private Configuration configuration = new Configuration(); | ||
public List<InnerObject> channels = new ArrayList<>(); | ||
|
||
public DummyObject() { | ||
configuration.put("testShort", Short.valueOf("12")); | ||
configuration.put("testInt", Integer.valueOf("12")); | ||
configuration.put("testLong", Long.valueOf("12")); | ||
configuration.put("testDouble", Double.valueOf("12.12")); | ||
configuration.put("testFloat", Float.valueOf("12.12")); | ||
configuration.put("testBigDecimal", new BigDecimal(12)); | ||
configuration.put("testBoolean", true); | ||
configuration.put("testString", "hello world"); | ||
configuration.put("multiInt", Arrays.asList(1, 2, 3)); | ||
|
||
InnerObject inner = new InnerObject(); | ||
inner.configuration.put("testChildLong", Long.valueOf("12")); | ||
channels.add(inner); | ||
} | ||
} | ||
|
||
private static class InnerObject { | ||
private Configuration configuration = new Configuration(); | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 0 additions & 70 deletions
70
...rage.json.test/src/main/java/org/eclipse/smarthome/storage/json/test/JSonStorageTest.java
This file was deleted.
Oops, something went wrong.
84 changes: 84 additions & 0 deletions
84
...rage.json/src/main/java/org/eclipse/smarthome/storage/json/ConfigurationDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* Copyright (c) 2014-2016 by the respective copyright holders. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
*/ | ||
package org.eclipse.smarthome.storage.json; | ||
|
||
import java.lang.reflect.Type; | ||
import java.math.BigDecimal; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Map.Entry; | ||
|
||
import org.eclipse.smarthome.config.core.Configuration; | ||
|
||
import com.google.gson.JsonArray; | ||
import com.google.gson.JsonDeserializationContext; | ||
import com.google.gson.JsonDeserializer; | ||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.JsonParseException; | ||
import com.google.gson.JsonPrimitive; | ||
|
||
/** | ||
* Deserializes a {@link Configuration} object. | ||
* | ||
* As opposed to Gson's default behavior, it ensures that all numbers are represented as {@link BigDecimal}s. | ||
* | ||
* @author Simon Kaufmann - initial contribution and API | ||
* | ||
*/ | ||
public class ConfigurationDeserializer implements JsonDeserializer<Configuration> { | ||
|
||
@Override | ||
public Configuration deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||
throws JsonParseException { | ||
Configuration configuration = new Configuration(); | ||
JsonObject configurationObject = json.getAsJsonObject(); | ||
JsonObject propertiesObject = configurationObject.get("properties").getAsJsonObject(); | ||
for (Entry<String, JsonElement> entry : propertiesObject.entrySet()) { | ||
JsonElement value = entry.getValue(); | ||
String key = entry.getKey(); | ||
if (value.isJsonPrimitive()) { | ||
JsonPrimitive primitive = value.getAsJsonPrimitive(); | ||
configuration.put(key, deserialize(primitive)); | ||
} else if (value.isJsonArray()) { | ||
JsonArray array = value.getAsJsonArray(); | ||
configuration.put(key, deserialize(array)); | ||
} else { | ||
throw new IllegalArgumentException( | ||
"Configuration parameters must be primitives or arrays of primities only but was " + value); | ||
} | ||
} | ||
return configuration; | ||
} | ||
|
||
private Object deserialize(JsonPrimitive primitive) { | ||
if (primitive.isString()) { | ||
return primitive.getAsString(); | ||
} else if (primitive.isNumber()) { | ||
return primitive.getAsBigDecimal(); | ||
} else if (primitive.isBoolean()) { | ||
return primitive.getAsBoolean(); | ||
} else { | ||
throw new IllegalArgumentException("Unsupported primitive: " + primitive); | ||
} | ||
} | ||
|
||
private Object deserialize(JsonArray array) { | ||
List<Object> list = new LinkedList<>(); | ||
for (JsonElement element : array) { | ||
if (element.isJsonPrimitive()) { | ||
JsonPrimitive primitive = element.getAsJsonPrimitive(); | ||
list.add(deserialize(primitive)); | ||
} else { | ||
throw new IllegalArgumentException("Multiples must only contain primitives but was " + element); | ||
} | ||
} | ||
return list; | ||
} | ||
|
||
} |
Oops, something went wrong.