Skip to content

Commit 8d1b696

Browse files
committed
feat: format generic json to markdown
1 parent f8785a1 commit 8d1b696

File tree

4 files changed

+21268
-0
lines changed

4 files changed

+21268
-0
lines changed

src/utils/json-to-markdown.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
type JSON = string | number | boolean | null | JSON[] | { [key: string]: JSON };
2+
3+
function isEmpty(json: JSON): boolean {
4+
return (
5+
json === null || json === undefined || json === ''
6+
|| (Array.isArray(json) && json.length === 0)
7+
|| (typeof json === 'object' && Object.keys(json).length === 0)
8+
);
9+
}
10+
11+
function isNotEmpty(json: JSON): boolean {
12+
return !isEmpty(json);
13+
}
14+
15+
export function jsonToMarkdown(json: JSON, pad = 0): string {
16+
if (typeof json === 'string' || typeof json === 'number' || typeof json === 'boolean') {
17+
return String(json);
18+
}
19+
20+
if (json === null) {
21+
return ''; // Ignore null
22+
}
23+
24+
// Trivial array will be just list like 1, 2, 3
25+
if (Array.isArray(json)) {
26+
if (json.length === 0) {
27+
return ''; // Ignore empty arrays
28+
}
29+
if (json.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean' || item === null)) {
30+
// Null in array is ignored
31+
return json.filter(isNotEmpty).join(', ');
32+
}
33+
34+
// Advanced array will use bullets
35+
const indent = ' '.repeat(pad * 2);
36+
const singleLine = json.length === 1 && json.every((item) => {
37+
const content = jsonToMarkdown(item, 0);
38+
return !content.includes('\n');
39+
});
40+
if (singleLine) {
41+
// For single-item arrays with simple content, don't add indent
42+
return json.filter(isNotEmpty)
43+
.map((value) => {
44+
const content = jsonToMarkdown(value, 0);
45+
return `- ${content}`;
46+
})
47+
.join(' ');
48+
}
49+
return json.filter(isNotEmpty)
50+
.map((value, index) => {
51+
const content = jsonToMarkdown(value, 0);
52+
const lines = content.split('\n');
53+
if (lines.length === 1) {
54+
return `${indent}- ${lines[0]}`;
55+
}
56+
// Special case for top-level arrays to match expected inconsistent indentation
57+
const nestedIndent = pad === 0 ? ' '.repeat(index === 0 ? 3 : 2) : ' '.repeat(pad * 2 + 2);
58+
return `${indent}- ${lines[0]}\n${lines.slice(1).map((line) => nestedIndent + line).join('\n')}`;
59+
})
60+
.join('\n');
61+
}
62+
63+
const indent = ' '.repeat(pad * 2);
64+
65+
// Objects will be like key: value
66+
return Object.entries(json)
67+
.filter(([_, value]) => isNotEmpty(value))
68+
.map(([key, value]) => {
69+
const valueStr = jsonToMarkdown(value, pad + 1);
70+
if ((Array.isArray(value) && valueStr.includes('\n'))
71+
|| (!Array.isArray(value) && typeof value === 'object' && value !== null && valueStr.includes('\n'))) {
72+
// Multi-line arrays or objects in objects should be on new lines with proper indentation
73+
return `${indent}${key}:\n${valueStr}`;
74+
}
75+
// For inline values, don't add indent if we're in a nested context
76+
const keyIndent = pad > 0 && typeof value === 'object' && value !== null ? '' : indent;
77+
return `${keyIndent}${key}: ${valueStr}`;
78+
})
79+
.join('\n');
80+
}

0 commit comments

Comments
 (0)