diff --git a/src/main/java/com/moandjiezana/toml/MapValueWriter.java b/src/main/java/com/moandjiezana/toml/MapValueWriter.java index d59bc65..9d6f5e4 100644 --- a/src/main/java/com/moandjiezana/toml/MapValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/MapValueWriter.java @@ -22,7 +22,7 @@ class MapValueWriter implements ValueWriter { public void write(Object value, WriterContext context) { Map from = (Map) value; - if (hasPrimitiveValues(from)) { + if (hasPrimitiveValues(from, context)) { context.writeKey(); } @@ -81,7 +81,7 @@ class MapValueWriter implements ValueWriter { return stringKey; } - private static boolean hasPrimitiveValues(Map values) { + private static boolean hasPrimitiveValues(Map values, WriterContext context) { for (Object key : values.keySet()) { Object fromValue = values.get(key); if (fromValue == null) { diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java index dcbb95e..bddccff 100644 --- a/src/main/java/com/moandjiezana/toml/TomlWriter.java +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -29,7 +29,7 @@ import static com.moandjiezana.toml.ValueWriters.WRITERS; public class TomlWriter { private WriterIndentationPolicy indentationPolicy = new WriterIndentationPolicy(); - private boolean wantTerseArraysValue = true; + private boolean wantTerseArraysValue = false; private GregorianCalendar calendar = new GregorianCalendar(); private DateFormat customDateFormat = null; diff --git a/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java b/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java index 2539a31..735cc5d 100644 --- a/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java +++ b/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java @@ -1,6 +1,8 @@ package com.moandjiezana.toml; -import static org.junit.Assert.assertEquals; +import com.google.gson.*; +import org.junit.Ignore; +import org.junit.Test; import java.io.IOException; import java.io.InputStream; @@ -8,24 +10,11 @@ import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Type; import java.text.DateFormat; +import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; +import java.util.*; -import org.junit.Ignore; -import org.junit.Test; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import static org.junit.Assert.assertEquals; public class BurntSushiValidTest { @@ -43,11 +32,13 @@ public class BurntSushiValidTest { @Test public void arrays_hetergeneous() throws Exception { run("arrays-hetergeneous"); + runEncoder("arrays-hetergeneous"); } @Test public void arrays_nested() throws Exception { run("arrays-nested"); + runEncoder("arrays-nested"); } @Test @@ -68,21 +59,25 @@ public class BurntSushiValidTest { @Test public void datetime() throws Exception { run("datetime"); + runEncoder("datetime"); } @Test public void empty() throws Exception { run("empty"); + runEncoder("empty"); } @Test public void example() throws Exception { run("example"); + runEncoder("example"); } @Test public void float_() throws Exception { run("float"); + runEncoder("float"); } @Test @@ -93,16 +88,19 @@ public class BurntSushiValidTest { @Test public void implicit_and_explicit_before() throws Exception { run("implicit-and-explicit-before"); + runEncoder("implicit-and-explicit-before"); } @Test public void implicit_groups() throws Exception { run("implicit-groups"); + runEncoder("implicit-groups"); } @Test public void integer() throws Exception { run("integer"); + runEncoder("integer"); } @Test @@ -128,6 +126,7 @@ public class BurntSushiValidTest { @Test public void key_special_chars_modified() throws Exception { run("key-special-chars-modified"); + runEncoder("key-special-chars-modified"); } @Test @Ignore @@ -143,11 +142,13 @@ public class BurntSushiValidTest { @Test public void long_float() throws Exception { run("long-float"); + runEncoder("long-float"); } @Test public void long_integer() throws Exception { run("long-integer"); + runEncoder("long-integer"); } @Test @Ignore @@ -173,6 +174,7 @@ public class BurntSushiValidTest { @Test public void string_empty() throws Exception { run("string-empty"); + runEncoder("string-empty"); } @Test @Ignore @@ -183,11 +185,13 @@ public class BurntSushiValidTest { @Test public void string_escapes_modified() throws Exception { run("string-escapes-modified"); + runEncoder("string-escapes-modified"); } @Test public void string_simple() throws Exception { run("string-simple"); + runEncoder("string-simple"); } @Test @@ -198,11 +202,13 @@ public class BurntSushiValidTest { @Test public void table_array_implicit() throws Exception { run("table-array-implicit"); + runEncoder("table-array-implicit"); } @Test public void table_array_many() throws Exception { run("table-array-many"); + runEncoder("table-array-many"); } @Test @@ -210,9 +216,17 @@ public class BurntSushiValidTest { run("table-array-nest"); } + @Test + public void table_array_nest_modified() throws Exception { + // Same test, but with stray spaces in the expected TOML removed + runEncoder("table-array-nest-modified", + new TomlWriter().setIndentationPolicy(new WriterIndentationPolicy().setTableIndent(2))); + } + @Test public void table_array_one() throws Exception { run("table-array-one"); + runEncoder("table-array-one"); } @Test @@ -246,7 +260,9 @@ public class BurntSushiValidTest { } private void run(String testName) { - InputStream inputToml = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml"); + InputStream inputTomlStream = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml"); + // InputStream inputToml = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml"); + String inputToml = convertStreamToString(inputTomlStream); Reader expectedJsonReader = new InputStreamReader(getClass().getResourceAsStream("burntsushi/valid/" + testName + ".json")); JsonElement expectedJson = GSON.fromJson(expectedJsonReader, JsonElement.class); @@ -254,16 +270,103 @@ public class BurntSushiValidTest { JsonElement actual = TEST_GSON.toJsonTree(toml).getAsJsonObject().get("values"); assertEquals(expectedJson, actual); - + try { - inputToml.close(); + inputTomlStream.close(); } catch (IOException e) {} try { expectedJsonReader.close(); } catch (IOException e) {} } - + + static String convertStreamToString(java.io.InputStream is) { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + private void runEncoder(String testName) { + runEncoder(testName, + new TomlWriter(). + wantTerseArrays(true). + setTimeZone(TimeZone.getTimeZone("UTC")). + setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"))); + } + + private void runEncoder(String testName, TomlWriter tomlWriter) { + InputStream inputTomlStream = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml"); + String expectedToml = convertStreamToString(inputTomlStream); + + Reader inputJsonReader = new InputStreamReader(getClass().getResourceAsStream("burntsushi/valid/" + testName + ".json")); + JsonElement jsonInput = GSON.fromJson(inputJsonReader, JsonElement.class); + Map enriched = enrichJson(jsonInput.getAsJsonObject()); + + String encoded = tomlWriter.write(enriched); + assertEquals(expectedToml, encoded); + } + + // Enrich toml-test JSON trees into native Java types, suiteable + // for consumption by TomlWriter. + private Map enrichJson(JsonObject jsonObject) { + Map enriched = new LinkedHashMap(); + for (Map.Entry entry : jsonObject.entrySet()) { + enriched.put(entry.getKey(), enrichJsonElement(entry.getValue())); + } + + return enriched; + } + + Object enrichJsonElement(JsonElement jsonElement) { + if (jsonElement.isJsonObject()) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + if (jsonObject.has("type") && jsonObject.has("value")) { + return enrichPrimitive(jsonObject); + } + return enrichJson(jsonElement.getAsJsonObject()); + } else if (jsonElement.isJsonArray()) { + List tables = new LinkedList(); + for (JsonElement arrayElement : jsonElement.getAsJsonArray()) { + tables.add(enrichJsonElement(arrayElement)); + } + + return tables; + } + + throw new AssertionError("received unexpected JsonElement: " + jsonElement); + } + + private Object enrichPrimitive(JsonObject jsonObject) { + String type = jsonObject.getAsJsonPrimitive("type").getAsString(); + if ("bool".equals(type)) { + return jsonObject.getAsJsonPrimitive("value").getAsBoolean(); + } else if ("integer".equals(type)) { + return jsonObject.getAsJsonPrimitive("value").getAsBigInteger(); + } else if ("float".equals(type)) { + return jsonObject.getAsJsonPrimitive("value").getAsDouble(); + } else if ("string".equals(type)) { + return jsonObject.getAsJsonPrimitive("value").getAsString(); + } else if ("datetime".equals(type)) { + DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC")); + String dateString = jsonObject.getAsJsonPrimitive("value").getAsString(); + try { + return iso8601Format.parse(dateString); + } catch (ParseException e) { + throw new AssertionError("failed to parse datetime '" + dateString + "': " + e.getMessage()); + } + } else if ("array".equals(type)) { + JsonArray jsonArray = jsonObject.getAsJsonArray("value"); + List enriched = new LinkedList(); + for (JsonElement arrayElement : jsonArray) { + enriched.add(enrichJsonElement(arrayElement)); + } + + return enriched; + } + + throw new AssertionError("enrichPrimitive: received unknown type " + type); + } + private static final Gson GSON = new Gson(); private static final Gson TEST_GSON = new GsonBuilder() .registerTypeAdapter(Boolean.class, serialize(Boolean.class)) diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index 9545e55..482fe3d 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -317,7 +317,7 @@ public class ValueWriterTest { @Test public void should_write_strings_to_toml_utf8() throws UnsupportedEncodingException { String input = " é foo € \b \t \n \f \r \" \\ "; - assertEquals("\" \\u00E9 foo \\u20AC \\b \\t \\n \\f \\r \\\" \\ \"", new TomlWriter().write(input)); + assertEquals("\" \\u00E9 foo \\u20AC \\b \\t \\n \\f \\r \\\" \\\\ \"", new TomlWriter().write(input)); // Check unicode code points greater than 0XFFFF input = " \uD801\uDC28 \uD840\uDC0B "; diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.json b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.json new file mode 100644 index 0000000..c117afa --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.json @@ -0,0 +1,18 @@ +{ + "albums": [ + { + "name": {"type": "string", "value": "Born to Run"}, + "songs": [ + {"name": {"type": "string", "value": "Jungleland"}}, + {"name": {"type": "string", "value": "Meeting Across the River"}} + ] + }, + { + "name": {"type": "string", "value": "Born in the USA"}, + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}}, + {"name": {"type": "string", "value": "Dancing in the Dark"}} + ] + } + ] +} diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.toml b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.toml new file mode 100644 index 0000000..ce3cae1 --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.toml @@ -0,0 +1,17 @@ +[[albums]] +name = "Born to Run" + + [[albums.songs]] + name = "Jungleland" + + [[albums.songs]] + name = "Meeting Across the River" + +[[albums]] +name = "Born in the USA" + + [[albums.songs]] + name = "Glory Days" + + [[albums.songs]] + name = "Dancing in the Dark"