diff --git a/README.md b/README.md index c8d3f49..021717f 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,22 @@ JsonValue name = Json.value("Alice"); JsonValue points = Json.value(23); ``` -And there are methods for creating empty arrays and objects as well. +There is also a variant of `value()` to create JSON values directly form any Java object: + +```java +Set javaValues = new HashSet(); +javaValues.add("me"); +javaValues.add(3.141); +javaValues.add(new Date()); +JsonValue jsonValues = Json.value(javaValues); +``` + +If you want to control the serialization of your own objects, implement the +`JsonSerializable` interface, which is respected by the `value()` method. +Any other object, which cannot be directly mapped to a JSON value is converted to a string using the +`toString()`method of the object. Circular references are not resolved. + +There are methods for creating empty arrays and objects as well. Use these together with `add` to create data structures: ```java @@ -146,7 +161,7 @@ You can also create JSON arrays conveniently from Java arrays such as `String[]` ```java String[] javaNames = {"Alice", "Bob"}; -JsonArray jsonNames = Json.array(names); +JsonArray jsonNames = Json.array(javaNames); ``` ### Modify JSON arrays and objects diff --git a/com.eclipsesource.json/src/main/java/com/eclipsesource/json/Json.java b/com.eclipsesource.json/src/main/java/com/eclipsesource/json/Json.java index 7bbc660..e316b0e 100644 --- a/com.eclipsesource.json/src/main/java/com/eclipsesource/json/Json.java +++ b/com.eclipsesource.json/src/main/java/com/eclipsesource/json/Json.java @@ -50,6 +50,14 @@ * String[] names = ... * JsonArray array = Json.array(names); * + *

+ * To create a JSON array from any given Java object, you can use the value(object) + * method: + *

+ *
+ * Object object = ...
+ * JsonValue json = Json.value(object);
+ * 
*/ public final class Json { @@ -144,6 +152,21 @@ public static JsonValue value(boolean value) { return value ? TRUE : FALSE; } + /** + * Returns a JsonValue instance that represents the given object. If the object implements + * {@link JsonSerializable}, its {@link JsonSerializable#asJsonValue()} method is called and the + * result returned. + *

+ * Converts null values to null literals and non-trivial objects to their string representation. + * + * @param object + * the object to get a JSON representation for + * @return a JSON value that represents the given object + */ + public static JsonValue value(Object object) { + return JsonBuilder.toJsonValue(object); + } + /** * Creates a new empty JsonArray. This is equivalent to creating a new JsonArray using the * constructor. diff --git a/com.eclipsesource.json/src/main/java/com/eclipsesource/json/JsonBuilder.java b/com.eclipsesource.json/src/main/java/com/eclipsesource/json/JsonBuilder.java new file mode 100644 index 0000000..cbe2362 --- /dev/null +++ b/com.eclipsesource.json/src/main/java/com/eclipsesource/json/JsonBuilder.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2013, 2015 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ + +package com.eclipsesource.json; + +import java.lang.reflect.Array; + +import java.util.Map; +import java.util.Map.Entry; + + +/** + * A simple builder which allows to create a JsonValue from a java object. As this is part of + * the minimal-json library, it is a minimal converter, and therefore does not resolve cyclic + * structures. + * + * @author Pascal Bihler + */ +public class JsonBuilder { + /** + * Creates a JsonValue from a Java object. If the object implements {@link JsonSerializable}, its + * {@link JsonSerializable#asJsonValue()} method is called and the result returned. + *

+ * Converts null values to null literals and non-trivial objects to their string representation. + * + * @param object + * The object to convert to json + * @return The Java object as {@link JsonValue} + */ + public static JsonValue toJsonValue(final Object object) { + if (object == null) { + return Json.NULL; + } else if (object instanceof JsonValue) { + return (JsonValue) object; + } else if (object instanceof JsonSerializable) { + return ((JsonSerializable)object).asJsonValue(); + } else if (object instanceof Boolean) { + return Json.value(((Boolean)object).booleanValue()); + } else if (object instanceof Byte) { + return Json.value(((Byte)object).byteValue()); + } else if (object instanceof Short) { + return Json.value(((Short)object).shortValue()); + } else if (object instanceof Integer) { + return Json.value(((Integer)object).intValue()); + } else if (object instanceof Long) { + return Json.value(((Long)object).longValue()); + } else if (object instanceof Float) { + return Json.value(((Float)object).floatValue()); + } else if (object instanceof Double) { + return Json.value(((Double)object).doubleValue()); + } else if (object.getClass().isArray()) { + return arrayToJsonValue(object); + } else if (object instanceof Iterable) { + return iterableToJsonValue((Iterable)object); + } else if (object instanceof Map) { + return mapToJsonValue((Map)object); + } else { + return Json.value(String.valueOf(object)); + } + } + + /** + * Creates a JsonArray from a collection object. + */ + static JsonArray iterableToJsonValue(final Iterable collection) { + final JsonArray array = new JsonArray(); + for (final Object element : collection) { + array.add(toJsonValue(element)); + } + return array; + } + + /** + * Creates a JsonObject from a Java Map + */ + static JsonObject mapToJsonValue(final Map map) { + final JsonObject object = new JsonObject(); + for (final Entry entry : map.entrySet()) { + object.add(String.valueOf(entry.getKey()), toJsonValue(entry.getValue())); + } + return object; + } + + /** + * Creates a JsonArray from an array. + */ + static JsonValue arrayToJsonValue(final Object inputArray) { + final JsonArray array = new JsonArray(); + final int arrayLength = Array.getLength(inputArray); + for (int i = 0; i < arrayLength; i++) { + final Object element = Array.get(inputArray, i); + array.add(toJsonValue(element)); + } + return array; + } +} diff --git a/com.eclipsesource.json/src/main/java/com/eclipsesource/json/JsonSerializable.java b/com.eclipsesource.json/src/main/java/com/eclipsesource/json/JsonSerializable.java new file mode 100644 index 0000000..0be52e6 --- /dev/null +++ b/com.eclipsesource.json/src/main/java/com/eclipsesource/json/JsonSerializable.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2017 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package com.eclipsesource.json; + +/** + * Objects, which implement this interface, can be directly serialized as {@link JsonValue}. + */ +public interface JsonSerializable { + + /** + * Serializes the object into a {@link JsonValue} + * + * @return The object as json representation + */ + public JsonValue asJsonValue(); +} diff --git a/com.eclipsesource.json/src/test/java/com/eclipsesource/json/JsonBuilder_Test.java b/com.eclipsesource.json/src/test/java/com/eclipsesource/json/JsonBuilder_Test.java new file mode 100644 index 0000000..baf87ec --- /dev/null +++ b/com.eclipsesource.json/src/test/java/com/eclipsesource/json/JsonBuilder_Test.java @@ -0,0 +1,267 @@ +package com.eclipsesource.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.Test; + + +@SuppressWarnings("boxing") +public class JsonBuilder_Test { + @Test + public void convertsNull() { + JsonValue value = JsonBuilder.toJsonValue((String)null); + assertEquals("null", value.toString()); + } + + @Test + public void convertsBooleanTrue() { + JsonValue value = JsonBuilder.toJsonValue(true); + assertEquals("true", value.toString()); + } + + @Test + public void convertsBooleanFalse() { + JsonValue value = JsonBuilder.toJsonValue(false); + assertEquals("false", value.toString()); + } + + @Test + public void convertsZeroInt() { + JsonValue value = JsonBuilder.toJsonValue(0); + assertEquals("0", value.toString()); + } + + @Test + public void convertsPositiveInt() { + JsonValue value = JsonBuilder.toJsonValue(44); + assertEquals("44", value.toString()); + } + + @Test + public void convertsNegativeInt() { + JsonValue value = JsonBuilder.toJsonValue(-10); + assertEquals("-10", value.toString()); + } + + @Test + public void convertsByte() { + JsonValue value = JsonBuilder.toJsonValue((byte)12); + assertEquals("12", value.toString()); + } + + @Test + public void convertsShort() { + JsonValue value = JsonBuilder.toJsonValue((short)23); + assertEquals("23", value.toString()); + } + + @Test + public void convertsLong() { + JsonValue value = JsonBuilder.toJsonValue(12340L); + assertEquals("12340", value.toString()); + } + + @Test + public void convertsFloat() { + JsonValue value = JsonBuilder.toJsonValue(3.141f); + assertEquals("3.141", value.toString()); + } + + @Test + public void convertsDouble() { + JsonValue value = JsonBuilder.toJsonValue(1e5d); + assertEquals("100000", value.toString()); + } + + @Test + public void convertsChar() { + JsonValue value = JsonBuilder.toJsonValue('%'); + assertEquals("\"%\"", value.toString()); + } + + @Test + public void convertsCharArray() { + final char[] charArray = "bla".toCharArray(); + JsonValue value = JsonBuilder.toJsonValue(charArray); + + JsonArray expected = Json.array("b","l","a"); + assertEquals(expected, value); + } + + @Test + public void convertsString() { + JsonValue value = JsonBuilder.toJsonValue("blubber\"Schnitzel\nmit\nPommes\""); + assertEquals("\"blubber\\\"Schnitzel\\nmit\\nPommes\\\"\"", value.toString()); + } + + @Test + public void convertsArbitraryObjectToString() { + JsonValue value = JsonBuilder.toJsonValue(new Object() { + @Override + public String toString() { + return "toString"; + } + }); + assertEquals("\"toString\"", value.toString()); + } + + @Test + public void passesJsonValuesTrough() { + final JsonValue expected = Json.value(123); + JsonValue value = JsonBuilder.toJsonValue(expected); + assertSame(expected, value); + } + + @Test + public void callsAsJsonValueOfAJsonSerializeableObject() { + final JsonValue expected = Json.value(123); + JsonValue value = JsonBuilder.toJsonValue(new JsonSerializable() { + + public JsonValue asJsonValue() { + return expected; + } + }); + assertSame(expected, value); + } + + @Test + public void convertsList() { + List list = new ArrayList(); + list.add("banane"); + list.add(3.141); + list.add(false); + + JsonValue value = JsonBuilder.toJsonValue(list); + assertEquals("[\"banane\",3.141,false]", value.toString()); + } + + @Test + public void convertsListWithNullEntries() { + List list = new ArrayList(); + list.add("banane"); + list.add(null); + list.add(null); + + JsonValue value = JsonBuilder.toJsonValue(list); + assertEquals("[\"banane\",null,null]", value.toString()); + } + + @Test + public void convertsNullList() { + List list = null; + JsonValue value = JsonBuilder.toJsonValue(list); + assertEquals("null", value.toString()); + } + + @Test + public void convertsEmptyList() { + List list = new ArrayList(); + JsonValue value = JsonBuilder.toJsonValue(list); + assertEquals("[]", value.toString()); + } + + @Test + public void convertsSet() { + Set set = new TreeSet(); + set.add("banane"); + set.add("23"); + set.add("banane"); + + JsonValue value = JsonBuilder.toJsonValue(set); + assertEquals("[\"23\",\"banane\"]", value.toString()); + } + + @Test + public void convertsNullSet() { + Set set = null; + JsonValue value = JsonBuilder.toJsonValue(set); + assertEquals("null", value.toString()); + } + + @Test + public void convertsMap() { + Map map = new LinkedHashMap(); + map.put("PI", 3.141); + map.put(true, "wahr"); + map.put("bla", "blubb"); + JsonValue value = JsonBuilder.toJsonValue(map); + assertEquals("{\"PI\":3.141,\"true\":\"wahr\",\"bla\":\"blubb\"}", value.toString()); + } + + @Test + public void convertsNullMap() { + Map map = null; + JsonValue value = JsonBuilder.toJsonValue(map); + assertEquals("null", value.toString()); + } + + @Test + public void convertsEmptyMap() { + Map map = new HashMap(); + JsonValue value = JsonBuilder.toJsonValue(map); + assertEquals("{}", value.toString()); + } + + @Test + public void convertsMapSetCombination() { + final Map> map = new LinkedHashMap>(); + final Set set = new TreeSet(); + set.add("A"); + set.add("B"); + set.add("C"); + map.put("ALL_TAGS", set); + map.put("NO_TAG", null); + + JsonValue value = JsonBuilder.toJsonValue(map); + assertEquals("{\"ALL_TAGS\":[\"A\",\"B\",\"C\"],\"NO_TAG\":null}", value.toString()); + } + + @Test + public void convertsArrayOfStrings() { + String[] array = new String[] {"A", "B", "C"}; + + JsonValue value = JsonBuilder.toJsonValue(array); + assertEquals("[\"A\",\"B\",\"C\"]", value.toString()); + } + + @Test + public void convertsArrayOfObjects() { + Object[] array = new Object[] {1, "B", 'c'}; + + JsonValue value = JsonBuilder.toJsonValue(array); + assertEquals("[1,\"B\",\"c\"]", value.toString()); + } + + @Test + public void convertsMultiDimensionalArray() { + Object[][] array = new Object[][] {new Object[] {1, 2, 3}, new Object[] {}}; + + JsonValue value = JsonBuilder.toJsonValue(array); + assertEquals("[[1,2,3],[]]", value.toString()); + } + + @Test + public void convertsNullArray() { + Object[] array = null; + + JsonValue value = JsonBuilder.toJsonValue(array); + assertEquals("null", value.toString()); + } + + @Test + public void convertsEmptyArray() { + Object[] array = new Object[] {}; + + JsonValue value = JsonBuilder.toJsonValue(array); + assertEquals("[]", value.toString()); + } +} diff --git a/com.eclipsesource.json/src/test/java/com/eclipsesource/json/Json_Test.java b/com.eclipsesource.json/src/test/java/com/eclipsesource/json/Json_Test.java index 26e3663..2874864 100644 --- a/com.eclipsesource.json/src/test/java/com/eclipsesource/json/Json_Test.java +++ b/com.eclipsesource.json/src/test/java/com/eclipsesource/json/Json_Test.java @@ -22,16 +22,23 @@ package com.eclipsesource.json; import static com.eclipsesource.json.TestUtil.assertException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import org.junit.Test; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import com.eclipsesource.json.TestUtil.RunnableEx; +import org.junit.Test; + public class Json_Test { @@ -146,6 +153,21 @@ public void value_string_toleratesNull() { assertSame(Json.NULL, Json.value(null)); } + @Test + public void value_complexObject_callsJsonBuilder() throws Exception + { + final Map> map = new LinkedHashMap>(); + final Set set = new TreeSet(); + set.add("A"); + set.add("B"); + set.add("C"); + map.put("ALL_TAGS", set); + map.put("NO_TAG", null); + + JsonValue value = Json.value(map); + assertEquals("{\"ALL_TAGS\":[\"A\",\"B\",\"C\"],\"NO_TAG\":null}", value.toString()); + } + @Test public void array() { assertEquals(new JsonArray(), Json.array());