---
title: "GoT Characters"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{GoT Characters}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## Overview
```{r setup}
library(jsontools)
got_json <- got_chars_json
```

First, let's find out what's the type of the JSON

```{r type}
json_type(got_json)
```

With `json_array_length()` and `json_array_types()` we can find out the number of elements in the array and the type of each element:

```{r array-infos}
json_array_length(got_json)
json_array_types(got_json)
```

Each element of the array is a JSON object. To work with the data in the array it is often more convenient to unpack it with `json_flatten()`.
This produces a JSON vector whose elements are the elements of the array:

```{r flatten}
json_flatten(got_json)
```

In non-interactive use it is a good idea to specify the type you expect via the `ptype` argument.
In this case we expect each element to be a JSON object and therefore use `ptype = new_json_object()`.

```{r flatten-ptype}
got_chars <- json_flatten(got_json, ptype = new_json_object())
```

An alternative would have been to use `ptype = json2()` when we don't care whether the elements are arrays, objects, or a mix of them.


## Extract Data
Let's have a look at the first Game of Thrones character

```{r got_chars_1}
got_chars[1]
```

This minified JSON is not so easy to read so we prettify it with `json_prettify()`

```{r prettify}
json_prettify(got_chars[1])
```

With the help of `json_extract()` we can now easily extract values from each character:

```{r extract-simple}
json_extract(got_chars, "$.name")
```

The second argument `"$.name"` specifies the `path` of the element we want to extract.
The basic syntax is relatively simple:

* `$.name` to extract the attribute `name` of a JSON object.
* `$[0]` to extract the first element of a JSON array. Keep in mind that the index here starts with 0 unlike in `R`!
* `$[last]` to extract the last element.

If you wonder about the dollar sign `$`: it stands for the current element.
Simply always start the path with it and you will be fine.

To extract elements of nested objects you simply combine the path.
For example the path to `1` in `{"a": {"b": [1, 2]}}` is `$.a.b[0]`.
You can find more examples in article [JSONpath - XPath for JSON](https://goessner.net/articles/JsonPath/index.html#e2) by Stefan GÃ¶ssner.

We can now construct a tibble with some basic information about the characters

```{r extract}
tibble::tibble(
  id = json_extract(got_chars, "$.id"),
  name = json_extract(got_chars, "$.name"),
  alive = json_extract(got_chars, "$.alive")
)
```

Oh, we actually also wanted the titles of each character

```{r error=TRUE}
tibble::tibble(
  id = json_extract(got_chars, "$.id"),
  name = json_extract(got_chars, "$.name"),
  alive = json_extract(got_chars, "$.alive"),
  titles = json_extract(got_chars, "$.titles")
)
```

Unfortunately, we get an error message saying that we cannot combine an array with text.
Let's try extracting the books as text:

```{r}
json_extract(got_chars, "$.titles", ptype = character())
```

We see that the API actually isn't type stable: it returns a simple text value instead of an array if the character only has one title.
It is usually not a good idea to mix characters and arrays in an API.
But as some APIs in the wild do this you can fix it with the argument `wrap_scalars` and get only JSON arrays back:

```{r}
json_extract(got_chars, "$.titles", wrap_scalars = TRUE)
```


## Unnesting
It would be quite tedious to extract every field of a character like that.
Instead you can simply use `json_unnest_wider()` to convert the keys of a JSON object into columns.
Like before, we also have to use `wrap_scalars = TRUE`:

```{r unnest-wider}
got_chars_df <- tibble::tibble(chars_json = got_chars) %>%
  json_unnest_wider(chars_json, wrap_scalars = TRUE)

got_chars_df
```

We can also unnest arrays with `json_unnest_longer()`.
This is basically a version `json_flatten()` for data frames

```{r unnest-longer}
got_chars_df[c("id", "name", "titles")] %>% 
  json_unnest_longer(titles)
```


## Modify Data
It is also possible to modify the JSON directly.
With `json_delete()` we can delete multiple fields in an object

```{r}
got_chars_small <- json_delete(got_chars, "$.url", "$.aliases", "$.allegiances")
```

And with `json_mutate()` we can easily mutate elements

```{r}
json_mutate(
  got_chars_small,
  .id = 1:5,
  .alive = !json_extract(got_chars_small, "$.alive")
)
```

or patch with another JSON using `json_merge()`

```{r}
x <- c(
  '{"id": 1, "a": 3}',
  '{"id": 2, "a": 4}',
  '{"id": 3}'
)

# remove element at "a"
json_merge(x, '{"a": null}')

# it is vectorised
json_merge(
  x,
  c(
    '{"a": null}',
    '{"a": 5}',
    '{"a": 6}'
  )
)
```
