This repository has been archived by the owner on Sep 19, 2023. It is now read-only.
/
json.ts
93 lines (73 loc) · 2.64 KB
/
json.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { Json, JsonPrimitive } from '@typed/common'
import { isObject } from './is'
// TODO: support Date
export type JsonSerializable =
| JsonPrimitive
| ReadonlyArray<JsonSerializable>
| JsonSerializableRecord
| ReadonlyMap<JsonSerializable, JsonSerializable>
| ReadonlySet<JsonSerializable>
export interface JsonSerializableRecord extends Record<string, JsonSerializable> {}
export const jsonReplace = (serializable: JsonSerializable) => replaceJson('', serializable)
export const jsonRevive = (json: Json) => reviveJson('', json)
export function fromJson(jsonString: string): JsonSerializable {
return JSON.parse(jsonString, reviveJson)
}
export function toJson<A extends JsonSerializable>(x: A, space?: string | number): string {
return JSON.stringify(x, replaceJson, space)
}
const JSON_TAG = '__json_tag__' as const
type JSON_TAG = typeof JSON_TAG
const VALUES_TAG = '__values_tag__' as const
type VALUES_TAG = typeof VALUES_TAG
enum Tag {
Set,
Map,
}
type TaggedJsonValues = {
[Tag.Map]: ReadonlyArray<readonly [Json, Json]>
[Tag.Set]: ReadonlyArray<Json>
}
type TaggedJson<A extends Tag> = {
readonly [JSON_TAG]: A
readonly [VALUES_TAG]: TaggedJsonValues[A]
}
const hasJsonTag = (x: unknown) => Object.prototype.hasOwnProperty.call(x, JSON_TAG)
const hasValuesTag = (x: unknown) => Object.prototype.hasOwnProperty.call(x, VALUES_TAG)
// Replace
function replaceJson(_: JsonSerializable, value: JsonSerializable): Json {
if (value instanceof Set) {
return { [JSON_TAG]: Tag.Set, [VALUES_TAG]: Array.from(value).map((x, i) => replaceJson(i, x)) }
}
if (value instanceof Map) {
return {
[JSON_TAG]: Tag.Map,
[VALUES_TAG]: Array.from(value.entries()).map(([key, value]) => [
replaceJson(key, key),
replaceJson(key, value),
]),
}
}
return value as Json
}
// Revive
function reviveJson(_: Json, value: Json): JsonSerializable {
if (isObject(value) && hasJsonTag(value) && hasValuesTag(value)) {
const { [JSON_TAG]: tag } = value as TaggedJson<Tag>
if (tag === Tag.Set) {
return new Set(reviveSetEntries((value as TaggedJson<Tag.Set>)[VALUES_TAG]))
}
if (tag === Tag.Map) {
return new Map(reviveMapEntries((value as TaggedJson<Tag.Map>)[VALUES_TAG]))
}
}
return value as JsonSerializable
}
function reviveSetEntries(entries: ReadonlyArray<Json>): ReadonlyArray<JsonSerializable> {
return entries.map((e, i) => reviveJson(i, e))
}
function reviveMapEntries(
entries: ReadonlyArray<readonly [Json, Json]>,
): ReadonlyArray<readonly [JsonSerializable, JsonSerializable]> {
return entries.map((e) => [reviveJson(e[0], e[0]), reviveJson(e[0], e[1])] as const)
}