Repository: alexcrichton/toml-rs (rust)

Issue:
Serializing Nested Array of Tables incorrectly
The TOML table equivalent to the following JSON:

```json
{"table": [
  {"inner": [ {} ] }
]}
```

should serialize into

```toml
[[table]]
  [[table.inner]]
```

Instead, it serializes into:
```toml
[[table.inner]]
```

which is equivalent to `{'table': {'inner': [{}]}}`.

Found with https://github.com/rust-fuzz/targets/pull/72.

Pull request description:
Serialize nested array of tables correctly
When serializing nested table like `[a.b.c]`, `toml` will leave out the super-tables if it can. Unfortunately, if one of the super-tables is actually an array of tables (`[[ a ]]`), then leaving out the super-table changes the value being encoded. For example:

```toml
[[ a ]]
[[ a.b ]]
```

which is equivalent to `{"a": [{"b": [{}]}]}` is currently serialized into

```toml
[[ a.b ]]
```

which is equivalent to `{"a": {"b": [{}]}}`, which is not the same.

To fix this, whenever we emit a table header, we check for  `[[ a ]]` parents and emit table headers for them first if necessary.

Closes #180 

Patch:
diff --git a/src/ser.rs b/src/ser.rs
index 22eda81..8f6b366 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -308,8 +308,28 @@ impl<'a> Serializer<'a> {
             State::Array { .. } => true,
             _ => false,
         };
+
+        // Unlike [..]s, we can't omit [[..]] ancestors, so be sure to emit table
+        // headers for them.
+        let mut p = state;
+        if let State::Array { first, parent, .. } = *state {
+            if first.get() {
+                p = parent;
+            }
+        }
+        while let State::Table { first, parent, .. } = *p {
+            p = parent;
+            if !first.get() {
+                break;
+            }
+            if let State::Array { parent: &State::Table {..}, ..} = *parent {
+                self.emit_table_header(parent)?;
+                break;
+            }
+        }
+
         match *state {
-            State::Table { first , .. } |
+            State::Table { first, .. } |
             State::Array { parent: &State::Table { first, .. }, .. } => {
                 if !first.get() {
                     self.dst.push_str("\n");


Test patch:
diff --git a/tests/serde.rs b/tests/serde.rs
index 0f4c37a..2f8d871 100644
--- a/tests/serde.rs
+++ b/tests/serde.rs
@@ -61,10 +61,6 @@ macro_rules! error {
     })
 }
 
-macro_rules! decode( ($t:expr) => ({
-    t!($t.try_into())
-}) );
-
 macro_rules! map( ($($k:ident: $v:expr),*) => ({
     let mut _m = BTreeMap::new();
     $(_m.insert(stringify!($k).to_string(), $v);)*
diff --git a/tests/valid.rs b/tests/valid.rs
index e8ea6af..676fc67 100644
--- a/tests/valid.rs
+++ b/tests/valid.rs
@@ -189,3 +189,7 @@ test!(example4,
 test!(example_bom,
        include_str!("valid/example-bom.toml"),
        include_str!("valid/example.json"));
+
+test!(table_array_nest_no_keys,
+      include_str!("valid/table-array-nest-no-keys.toml"),
+      include_str!("valid/table-array-nest-no-keys.json"));
diff --git a/tests/valid/table-array-nest-no-keys.json b/tests/valid/table-array-nest-no-keys.json
new file mode 100644
index 0000000..7537b1a
--- /dev/null
+++ b/tests/valid/table-array-nest-no-keys.json
@@ -0,0 +1,14 @@
+{
+    "albums": [
+        {
+            "songs": [{}, {}]
+        }
+    ],
+    "artists": [
+        {
+            "home": {
+                "address": {}
+            }
+        }
+    ]
+}
diff --git a/tests/valid/table-array-nest-no-keys.toml b/tests/valid/table-array-nest-no-keys.toml
new file mode 100644
index 0000000..ad6eb10
--- /dev/null
+++ b/tests/valid/table-array-nest-no-keys.toml
@@ -0,0 +1,6 @@
+[[ albums ]]
+  [[ albums.songs ]]
+  [[ albums.songs ]]
+
+[[ artists ]]
+  [ artists.home.address ]