Vue 3.0を用いてJSON形式のデータをツリー表示

# はじめに
Vue 3.0を書いていてJSONをツリー表示させたくなる機会がありました。
調べれば[jsTree](https://www.jstree.com/)や[vue-json-tree](https://www.npmjs.com/package/vue-json-tree), [vue-json-tree-view](https://www.npmjs.com/package/vue-json-tree-view)など出てきますが、今回はいずれも使わずにやってみます。

## 本記事の目標

```json
{
    "hoge": "fuga",
    "foo": "bar",
    "nums": [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
    "hashLists": [
        {
            "id": 0,
            "value": "a"
        },
        {
            "id": 1,
            "value": "b"
        },
        {
            "id": 3,
            "value": "c"
        }
    ],
    "bool-1": true,
    "bool-0": false
}
```

上記のJSONデータが、

---

- hoge: fuga
- foo: bar
- nums
  - 1
  - 2
  - 3
  - 4
  - 5
  - 6
  - 7
  - 8
  - 9
  - 0
- hashLists
  - 0
     - id: 0
     - value: a
  - 1
     - id: 1
     - value: b
  - 2
     - id: 3
     - value: c
- bool-1: true
- bool-0: false

---

と表示されることを目指します。

# 環境

```
node v14.3.0
@vue/cli 4.5.6
Google Chrome 86.0.4240.75
```

[vue-cliで手っ取り早くVue 3 + TypeScriptのプロジェクトを作ろう！](https://qiita.com/jesus_isao/items/0b305c7d90c45ad66c1b)を参考に作成したプロジェクト内でやっていきます。

## ディレクトリ構成

src内だけ載せます。

```
src/
├── App.vue
├── main.ts
├── shims-vue.d.ts
├── assets
└── components
        └── json-to-tree.vue
```

# コード
## App.vue

`state.content`にjsonデータをそのまま持たせています。

```vue
<template>
  <jsonToTree :content="state.content"/>
</template>

<script lang="ts">
import { defineComponent, reactive } from 'vue';
import jsonToTree from './components/json-to-tree.vue';

type State = {
  content: object;
};

export default defineComponent({
  name: 'App',
  components: {
    jsonToTree
  },
  setup() {
    // property
    const state = reactive<State>({
      content: {
        "hoge": "fuga",
        "foo": "bar",
        "nums": [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
        "hashLists": [
          {
            "id": 0,
            "value": "a"
          },
          {
            "id": 1,
            "value": "b"
          },
          {
            "id": 3,
            "value": "c"
          }
        ],
        "bool-1": true,
        "bool-0": false
      }
    });
    return {
      state,
    };
  }
});
</script>
```

## json-to-tree.vue

`v-if`や`v-for`を用いて再帰させることで実現しました。

```vue
<template>
    <div class="json-to-tree">
        <ul v-for="(value, key) in state.content" :key="key">
            <li v-if="!value || typeof value !== 'object'">
                <span v-if="!Array.isArray(state.content)">{{ key }}: </span>
                {{ value + "" }}
            </li>
            <li v-else>
                {{ key }}
                <json-to-tree :content="value"/>
            </li>
        </ul>
    </div>
</template>

<script lang="ts">
import { defineComponent, reactive, watch } from 'vue';

type Props = {
    content: object;
};
type State = {
    content: object;
};

export default defineComponent({
    name: "json-to-tree",
    props: {
        content: {
            type: Object,
            default() {
                return {}
            }
        }
    },
    setup(props: Props) {
        // property
        const state = reactive<State>({
            content: props.content
        });
        // watcher
        watch(props, (newVal) =>{
            state.content = newVal.content;
        });
        return {
            state
        }
    }
});
</script>

<style scoped>
.json-to-tree {
    margin: 0;
    padding: 0;
}
</style>
```

### 解説

わかりにくいのでいくつか解説します。

- `<li v-if="!value || typeof value !== 'object'">`  
object(ArrayとHashを想定)でない場合、出力します。
ここで、`!value || `としているのは、nullの場合、`typeof null`がobject型になってしまうためです。
- `<span v-if="!Array.isArray(state.content)">{{ key }}: </span>`  
元がHashでなくArrayだった場合、keyの出力は要らないため、[Array.isArray](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)を用いています。"元"がどちらかを見たいため、引数は`state.content`としている点に注意してください。
- `{{ value + "" }}`  
nullもfalseもundefinedも文字列にしないと表示されないため、文字列にしています。
- `<json-to-tree :content="value"/>`  
もしobject(ArrayとHashを想定)だった場合、再帰させます。これで階層を表現します。

# おわりに

とりあえずDOMツリーにできたので、ここからCSSで装飾して木構造らしくするなり、エディタのディレクトリ構造みたく開閉式で見やすくするなりしてくれればいいと思います。
簡単に書き殴ったんで誤字脱字はご容赦ください。
ありがとうございました。