5

I am converting a nested list of a specific structure (required by an API) to JSON using toJSON() in jsonlite. However, I need the final JSON to not contain the outer square brackets (also required by an API).

test_list <- list(
    list(formName = "test_title", user_id = "test_userid", rows = list(list(row = 0))), 
    list(formName = "test_title2", user_id = "test_userid2", rows = list(list(row = 0)))
)

jsonlite::toJSON(test_list, pretty = TRUE, auto_unbox = TRUE)

Which gives:

[
  {
    "formName": "test_title",
    "user_id": "test_userid",
    "rows": [
      {
        "row": 0
      }
    ]
  },
  {
    "formName": "test_title2",
    "user_id": "test_userid2",
    "rows": [
      {
        "row": 0
      }
    ]
  }
] 

But I need to remove the first and last square bracket.

I can use purrr::flatten() to remove the top level of the list and thus the square brackets in the JSON, but then toJSON() doesn't seem to like that the list has duplicate names, and renames them to name.1, name.2, name.3 etc (which also isn't allowed by the API).

That is:

jsonlite::toJSON(test_list %>% purrr::flatten(), pretty = TRUE, auto_unbox = TRUE)

Which removes the outer square brackets, but converts the names in the second element to formName.1, user_id.1, rows.1, like so:

{
  "formName": "test_title",
  "user_id": "test_userid",
  "rows": [
    {
      "row": 0
    }
  ],
  "formName.1": "test_title2",
  "user_id.1": "test_userid2",
  "rows.1": [
    {
      "row": 0
    }
  ]
}

But this is what I need:

{
  "formName": "test_title",
  "user_id": "test_userid",
  "rows": [
    {
      "row": 0
    }
  ],
  "formName": "test_title2",
  "user_id": "test_userid2",
  "rows": [
    {
      "row": 0
    }
  ]
}

That is, formName, user_ud and rows in the second JSON element are not appended with ".1"

Any advice would be greatly appreciated!

ztp
  • 97
  • 2
  • 9
  • 1
    the expected and input are the same. Did you meant `jsonlite::toJSON(test_list %>% purrr::flatten(), pretty = TRUE, auto_unbox = TRUE)` – akrun May 09 '19 at 06:06
  • @akrun Thanks for replying, but they are not the same. Notice how the second element in the first example have formName.1, user_id.1 and rows.1 -- these need to be just formName, user_id and rows. However, when using flatten() on the list, which I need to do to remove the square brackets that encapsulate the entire JSON, the ".1" is appended, which the API will not recognise. I have added auto_unbox = TRUE in the example to remove ambiguity. – ztp May 09 '19 at 06:12
  • 1
    If you paste your desired result into a JSON validator [like this one](https://jsonlint.com/) you'll see that it's not valid--duplicate key names are forbidden in correct JSON. I don't think you'll be able to get `toJSON` to produce invalid syntax, so I think your best bet is editing the result. – Gregor Thomas May 09 '19 at 06:13
  • Save `output = toJSON(test_list %>% purrr::flatten(), pretty = TRUE, auto_unbox = TRUE)` as an intermediate result, and then use `gsub` or similar to make the edits you need. – Gregor Thomas May 09 '19 at 06:15
  • @Gregor You're absolutely right. I had actually tried that approach but was using str_sub from stringr which converted the json into a character. I had just assumed that gsub would do the same. gsub does the trick, thanks! I'll accept your answer as correct – ztp May 09 '19 at 06:33
  • I'm really shocked there's a difference. `is.character(toJSON(...))` returns TRUE, but `stringr` seems to drop the `json` class... – Gregor Thomas May 09 '19 at 06:59
  • It's still unclear whether the expected output is of the form `{ "formName": "test_title", ... , "formName": "test_title2", ... }` (with duplicate keys, corresponding to OP's _"But this is what I need"_), or `{ "formName": "test_title", ... }, { "formName": "test_title2", ... }` (several valid JSON objects, comma separated, corresponding to OP's _"But I need to remove the first and last square bracket"_). In case it's the latter, would `test_list %>% map_chr(toJSON, pretty = TRUE, auto_unbox = TRUE) %>% paste(collapse = ",\n") %>% cat()` do? – Aurèle May 09 '19 at 14:25

2 Answers2

5

This seems to work (ugly but simple):

df = data.frame(a=1,b=2)
js = toJSON(unbox(fromJSON(toJSON(df))))
> js
{"a":1,"b":2} 

for some reason, auto_unbox=T does not work:

> toJSON(df,auto_unbox = T)
[{"a":1,"b":2}] 
Matija Cerne
  • 127
  • 1
  • 9
3

Just edit the JSON. You can do it with gsub, or with stringr. If you use stringr functions, it will lose it's "json" class, but you can put it back:

> x = jsonlite::toJSON(test_list %>% purrr::flatten(), pretty = TRUE, auto_unbox = TRUE)
> gsub("user_id\\.1", "user_id", x)
{
  "formName": "test_title",
  "user_id": "test_userid",
  "rows": [
    {
      "row": 0
    }
  ],
  "formName.1": "test_title2",
  "user_id": "test_userid2",
  "rows.1": [
    {
      "row": 0
    }
  ]
}

> y = stringr::str_replace_all(x, "user_id\\.1", "user_id")
> class(y) = "json"
> y
{
  "formName": "test_title",
  "user_id": "test_userid",
  "rows": [
    {
      "row": 0
    }
  ],
  "formName.1": "test_title2",
  "user_id": "test_userid2",
  "rows.1": [
    {
      "row": 0
    }
  ]
} 

I'll leave it to you to write appropriate regex to make the substitutions you want.

Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294