Skip to content

Commit f560951

Browse files
committed
構造ビュー
1 parent 106946c commit f560951

File tree

11 files changed

+11296
-136
lines changed

11 files changed

+11296
-136
lines changed

components/json/JsonStructure.tsx

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { JSONArrayValueStruct, JSONBooleanValueStruct, JSONNullValueStruct, JSONNumberValueStruct, JSONObjectValueStruct, JSONStringValueStruct, JSONValueStruct, jsonItemCount } from "@/libs/json"
2+
import React from "react";
3+
import { MdDataArray, MdDataObject } from 'react-icons/md';
4+
import { BsToggleOff, BsToggleOn } from 'react-icons/bs';
5+
import { TiSortAlphabetically, TiSortNumerically } from 'react-icons/ti';
6+
import { InlineIcon } from "../lv1/InlineIcon";
7+
import _ from "lodash";
8+
9+
10+
const IconString = TiSortAlphabetically;
11+
const IconNumber = TiSortNumerically;
12+
const IconTrue = BsToggleOn;
13+
const IconFalse = BsToggleOff;
14+
const IconObject = MdDataObject;
15+
const IconArray = MdDataArray;
16+
17+
18+
const ActualIconForType = (props: {
19+
json: JSONValueStruct;
20+
}) => {
21+
switch (props.json.typename) {
22+
case "string":
23+
return IconString;
24+
case "number":
25+
return IconNumber;
26+
case "boolean":
27+
if (props.json.value) {
28+
return IconTrue;
29+
} else {
30+
return IconFalse;
31+
}
32+
case "object":
33+
return IconObject;
34+
case "array":
35+
return IconArray;
36+
default:
37+
return null;
38+
}
39+
};
40+
41+
const IconForType = (props: {
42+
json: JSONValueStruct;
43+
}) => {
44+
const Icon = ActualIconForType(props);
45+
if (!Icon) { return null; }
46+
return <InlineIcon className="text-xl" i={<Icon className="text-xl" />} />
47+
}
48+
49+
const JsonNullStructure = (props: {
50+
jkey?: string;
51+
js: JSONNullValueStruct;
52+
}) => {
53+
return (<div className="json-structure json-null-structure">
54+
<p className="value-null">(null)</p>
55+
</div>);
56+
}
57+
58+
const JsonStringStructure = (props: {
59+
jkey?: string;
60+
js: JSONStringValueStruct;
61+
}) => {
62+
return (<div className="json-structure json-string-structure flex items-center">
63+
<p className="item-value">
64+
&quot;{props.js.value}&quot;
65+
</p>
66+
</div>);
67+
}
68+
69+
const JsonNumberStructure = (props: {
70+
jkey?: string;
71+
js: JSONNumberValueStruct;
72+
}) => {
73+
return (<div className="json-structure json-number-structure flex items-center">
74+
<p className="item-value">
75+
{props.js.value}
76+
</p>
77+
</div>);
78+
}
79+
80+
const JsonBooleanStructure = (props: {
81+
jkey?: string;
82+
js: JSONBooleanValueStruct;
83+
}) => {
84+
return (<div className="json-structure json-boolean-structure flex items-center">
85+
<p className="item-value">
86+
{props.js.value
87+
? <span className="value-true">True</span>
88+
: <span className="value-false">False</span>}
89+
</p>
90+
</div>);
91+
}
92+
93+
const JsonObjectStructure = (props: {
94+
jkey?: string;
95+
js: JSONObjectValueStruct;
96+
}) => {
97+
return (<div className="json-structure json-object-structure grid grid-cols-[max-content_1fr] justify-stretch">
98+
<p className="schema-key">
99+
<span className="p-1">key</span>
100+
</p>
101+
<p className="schema-value">
102+
<span className="p-1">value</span>
103+
</p>
104+
{props.js.subtree.map((v) => {
105+
const [k, value] = v;
106+
const key = `${props.jkey ? props.jkey + "." : ""}${k}`;
107+
const is_structural = value.typename === "object" || value.typename === "array";
108+
const keyClassName = is_structural ? "item-key flex items-start" : "item-key flex items-center";
109+
const itemCount = jsonItemCount(value);
110+
return (
111+
<React.Fragment key={key}>
112+
<div className={keyClassName}>
113+
<div className="sticky top-0 flex items-center">
114+
{is_structural ? null : <IconForType json={value} /> }
115+
<p className="key p-1">{k}</p>
116+
{is_structural ? <IconForType json={value} /> : null }
117+
{ _.isFinite(itemCount) ? <p className="p-1">({itemCount})</p> : null }
118+
</div>
119+
</div>
120+
<div className="item-value flex items-center pl-1">
121+
<JsonStructure jkey={key} js={value} />
122+
</div>
123+
</React.Fragment>
124+
);
125+
})}
126+
</div>);
127+
}
128+
129+
const JsonArrayStructure = (props: {
130+
jkey?: string;
131+
js: JSONArrayValueStruct;
132+
}) => {
133+
return (<div className="json-structure json-array-structure grid grid-cols-[max-content_1fr] justify-stretch">
134+
<p className="schema-index">
135+
<span className="p-1">idx</span>
136+
</p>
137+
<p className="schema-count">
138+
</p>
139+
140+
{props.js.subarray.map((v, i) => {
141+
const key = `${props.jkey ? props.jkey + "." : ""}${i}`;
142+
const is_structural = v.typename === "object" || v.typename === "array";
143+
const keyClassName = is_structural ? "item-index flex items-start" : "item-index flex items-center";
144+
const itemCount = jsonItemCount(v);
145+
return (<React.Fragment key={key}>
146+
147+
<div className={keyClassName}>
148+
<div className="sticky top-0 flex items-center">
149+
{is_structural ? null: <IconForType json={v} /> }
150+
<p className="index p-1 font-bold">#{i}</p>
151+
{is_structural ? <IconForType json={v} /> : null }
152+
{ _.isFinite(itemCount) ? <p className="p-1">({itemCount})</p> : null }
153+
</div>
154+
</div>
155+
156+
<div className="item-value flex items-center pl-1">
157+
<JsonStructure jkey={key} js={v} />
158+
</div>
159+
160+
</React.Fragment>)
161+
})}
162+
</div>);
163+
}
164+
165+
export const JsonStructure = (props: {
166+
jkey?: string;
167+
js: JSONValueStruct;
168+
}) => {
169+
if (props.js.typename === "string") {
170+
return <JsonStringStructure jkey={props.jkey} js={props.js} />
171+
}
172+
if (props.js.typename === "number") {
173+
return <JsonNumberStructure jkey={props.jkey} js={props.js} />
174+
}
175+
if (props.js.typename === "boolean") {
176+
return <JsonBooleanStructure jkey={props.jkey} js={props.js} />
177+
}
178+
if (props.js.typename === "object") {
179+
return <JsonObjectStructure jkey={props.jkey} js={props.js} />
180+
}
181+
if (props.js.typename === "array") {
182+
return <JsonArrayStructure jkey={props.jkey} js={props.js} />
183+
}
184+
if (props.js.typename === "null") {
185+
return <JsonNullStructure jkey={props.jkey} js={props.js} />
186+
}
187+
return null;
188+
};

components/lv1/InlineIcon.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type Props = {
2+
i: JSX.Element;
3+
className?: string;
4+
};
5+
export const InlineIcon = ({ i, className }: Props) => {
6+
return (
7+
<span className={`inline-block ${className || 'p-1'} align-middle`}>{i}</span>
8+
);
9+
};

libs/json.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import _ from "lodash";
2+
3+
type JSONPrimitiveValue = string | number | boolean | null;
4+
type JSONStructuralValue = { [key: string]: JSONValue } | JSONValue[];
5+
type JSONValue = JSONPrimitiveValue | JSONStructuralValue;
6+
7+
const JSONPrimitiveValueTypes = ["string", "number", "boolean", "null"] as const;
8+
type JSONPrimitiveValueType = typeof JSONPrimitiveValueTypes[number];
9+
const JSONStructuralValueTypes = ["object", "array"] as const;
10+
type JSONStructuralValueType = typeof JSONStructuralValueTypes[number];
11+
export const JSONValueTypes = [...JSONPrimitiveValueTypes, ...JSONStructuralValueTypes] as const;
12+
export type JSONValueType = typeof JSONValueTypes[number];
13+
14+
export type JSONStringValueStruct = {
15+
typename: "string";
16+
value: string;
17+
};
18+
export type JSONNumberValueStruct = {
19+
typename: "number";
20+
value: number;
21+
};
22+
export type JSONBooleanValueStruct = {
23+
typename: "boolean";
24+
value: boolean;
25+
};
26+
export type JSONNullValueStruct = {
27+
typename: "null";
28+
value: null;
29+
};
30+
export type JSONObjectValueStruct = {
31+
typename: "object";
32+
value: { [key: string]: JSONValue };
33+
subtree: Array<[string, JSONValueStruct]>;
34+
};
35+
export type JSONArrayValueStruct = {
36+
typename: "array";
37+
value: JSONValue[];
38+
subarray: Array<JSONValueStruct>;
39+
};
40+
41+
export type JSONValueStruct = JSONStringValueStruct | JSONNumberValueStruct | JSONBooleanValueStruct | JSONNullValueStruct | JSONObjectValueStruct | JSONArrayValueStruct;
42+
43+
export function structurizeJSON(json: any): JSONValueStruct {
44+
if (_.isNull(json)) {
45+
return {
46+
typename: "null",
47+
value: json,
48+
};
49+
}
50+
51+
if (_.isString(json)) {
52+
return {
53+
typename: "string",
54+
value: json,
55+
};
56+
}
57+
if (_.isNumber(json)) {
58+
return {
59+
typename: "number",
60+
value: json,
61+
};
62+
}
63+
if (_.isBoolean(json)) {
64+
return {
65+
typename: "boolean",
66+
value: json,
67+
};
68+
}
69+
if (_.isArray(json)) {
70+
return {
71+
typename: "array",
72+
value: json,
73+
subarray: json.map((value) => structurizeJSON(value)),
74+
};
75+
}
76+
if (typeof json === "object") {
77+
return {
78+
typename: "object",
79+
value: json,
80+
subtree: _.map(json, (value, key) => [key, structurizeJSON(value)]),
81+
};
82+
}
83+
throw new Error("unreachable");
84+
}
85+
86+
export function jsonItemCount(json: JSONValueStruct) {
87+
switch (json.typename) {
88+
case "string":
89+
case "number":
90+
case "boolean":
91+
case "null":
92+
return null;
93+
case "array":
94+
return json.subarray.length;
95+
case "object":
96+
return json.subtree.length;
97+
}
98+
}

next.config.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
/** @type {import('next').NextConfig} */
2+
const isProduction = process.env.NODE_ENV === 'production';
23
const nextConfig = {
34
reactStrictMode: true,
4-
}
5+
images: {
6+
unoptimized: true,
7+
},
8+
env: {
9+
ne: process.env.NODE_ENV,
10+
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID,
11+
isProduction,
12+
},
13+
};
514

615
module.exports = nextConfig

0 commit comments

Comments
 (0)