Skip to content
Source
Sign inSign up
Open History to see the last commit

schemaJson.ts

105 lines2.8 KB

1import * as Effect from "effect/Effect";
2import * as Exit from "effect/Exit";
3import * as Result from "effect/Result";
4import * as Schema from "effect/Schema";
5import * as SchemaGetter from "effect/SchemaGetter";
6import * as SchemaTransformation from "effect/SchemaTransformation";
7​
8export const decodeJsonResult = <S extends Schema.Codec<unknown, unknown, never, never>>(
9 schema: S,
10) => {
11 const decode = Schema.decodeExit(Schema.fromJsonString(schema));
12 return (input: string) => {
13 const result = decode(input);
14 if (Exit.isFailure(result)) {
15 return Result.fail(result.cause);
16 }
17 return Result.succeed(result.value);
18 };
19};
20​
21const decodeJsonString = Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown));
22​
23const parseLenientJsonGetter = SchemaGetter.transformEffect((input: string) => {
24 // Match comments together so URLs stay inside blocks; whitespace keeps adjacent tokens apart.
25 let stripped = input.replace(
26 /("(?:[^"\\]|\\.)*")|\/\/[^\r\n]*|\/\*[\s\S]*?\*\//g,
27 (match, stringLiteral: string | undefined) => (stringLiteral ? match : " "),
28 );
29​
30 stripped = stripped.replace(
31 /("(?:[^"\\]|\\.)*")|,(\s*[}\]])/g,
32 (match, stringLiteral: string | undefined, bracket: string | undefined) =>
33 stringLiteral ? match : (bracket ?? ""),
34 );
35​
36 return decodeJsonString(stripped).pipe(Effect.mapError((error) => error.issue));
37});
38​
39const fromLenientJsonString = new SchemaTransformation.Transformation(
40 parseLenientJsonGetter,
41 SchemaGetter.stringifyJson(),
42);
43​
44const prettyJsonString = SchemaGetter.parseJson<string>().pipe(
45 SchemaGetter.compose(SchemaGetter.stringifyJson({ space: 2 })),
46);
47​
48export const fromLenientJson = <S extends Schema.Top>(schema: S) =>
49 Schema.String.pipe(Schema.decodeTo(schema, fromLenientJsonString));
50​
51export function extractJsonObject(raw: string): string {
52 const trimmed = raw.trim();
53 if (trimmed.length === 0) {
54 return trimmed;
55 }
56​
57 const start = trimmed.indexOf("{");
58 if (start < 0) {
59 return trimmed;
60 }
61​
62 let depth = 0;
63 let inString = false;
64 let escaping = false;
65 for (let index = start; index < trimmed.length; index += 1) {
66 const char = trimmed[index];
67 if (inString) {
68 if (escaping) {
69 escaping = false;
70 } else if (char === "\\") {
71 escaping = true;
72 } else if (char === '"') {
73 inString = false;
74 }
75 continue;
76 }
77​
78 if (char === '"') {
79 inString = true;
80 continue;
81 }
82​
83 if (char === "{") {
84 depth += 1;
85 continue;
86 }
87​
88 if (char === "}") {
89 depth -= 1;
90 if (depth === 0) {
91 return trimmed.slice(start, index + 1);
92 }
93 }
94 }
95​
96 return trimmed.slice(start);
98​
99export const fromJsonStringPretty = <S extends Schema.Top>(schema: S) =>
100 Schema.fromJsonString(schema).pipe(
101 Schema.encode({
102 decode: prettyJsonString,
103 encode: prettyJsonString,
104 }),
105 );