はじめに
Vue 3.0を書いていてJSONをツリー表示させたくなる機会がありました。
調べればjsTreeやvue-json-tree, vue-json-tree-viewなど出てきますが、今回はいずれも使わずにやってみます。
本記事の目標
{
"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
- 0
- 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のプロジェクトを作ろう!を参考に作成したプロジェクト内でやっていきます。
ディレクトリ構成
src内だけ載せます。
src/
├── App.vue
├── main.ts
├── shims-vue.d.ts
├── assets
└── components
└── json-to-tree.vue
コード
App.vue
state.content
にjsonデータをそのまま持たせています。
<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
を用いて再帰させることで実現しました。
<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を用いています。"元"がどちらかを見たいため、引数はstate.content
としている点に注意してください。 -
{{ value + "" }}
nullもfalseもundefinedも文字列にしないと表示されないため、文字列にしています。 -
<json-to-tree :content="value"/>
もしobject(ArrayとHashを想定)だった場合、再帰させます。これで階層を表現します。
おわりに
とりあえずDOMツリーにできたので、ここからCSSで装飾して木構造らしくするなり、エディタのディレクトリ構造みたく開閉式で見やすくするなりしてくれればいいと思います。
簡単に書き殴ったんで誤字脱字はご容赦ください。
ありがとうございました。