Show HN: JSON to Md(gfm)

rajnandan11 pts0 comments

json-to-md | JSON→Markdown conversion that runs byte-identical in the browser, Node, Go, and CLI

json-to-md

json-to-md

Convert one JSON document into deterministic, human-readable GitHub Flavored Markdown.

The same conversion runs in the browser, in Node, in Go, and on the command line. The TypeScript and Go implementations produce byte-identical output , enforced by a shared corpus/ and a cross-implementation fuzz gate in CI. Output is a readable projection rather than a reversible serialization format. Every document begins with a # Results heading and uses canonical spacing: LF endings, one blank line between blocks, no trailing spaces, one final newline.

Motivation

If you feed JSON into an LLM, you pay for its punctuation. Every {, }, ",<br>:, and , is tokens spent on structure the model does not need to read the<br>data. Converting the same document to Markdown headings and lists is measurably<br>cheaper and easier for models to follow:

Fewer tokens. A [tiktoken measurement][md-tokens] of one real document<br>came out to 13,869 tokens as JSON versus 11,612 as Markdown — about 16% less.<br>Reports in the wild put the JSON tax anywhere from [15–20%][md-reddit] up to 2x<br>depending on how nested and quote-heavy the data is.

Native format. Markdown is the lingua franca of LLM training corpora, so<br>it tends to tokenize efficiently and models parse its structure reliably.

Human-readable in the loop. Prompts, agent memory logs, and RAG context<br>are easier to read, diff, and hand-edit as Markdown than as escaped JSON — and<br>Markdown chunks concatenate cleanly, where stringified JSON does not.

Fits tighter context windows. In IDE assistants and agent loops where<br>context is aggressively trimmed, token-heavy JSON is a real risk; a leaner<br>projection leaves more room for the actual task.

The tradeoff: this is a one-way, human-facing projection , not a reversible<br>serialization. When a downstream step must parse, validate, or store the data,<br>keep the JSON. A common pattern is JSON for the machine contract and Markdown<br>for everything a model or a person has to read.

Node

Install

npm install @rajnandan1/json-to-md # or: pnpm add / yarn add

Ships ESM and CommonJS builds with TypeScript declarations, and no runtime dependencies. Node ≥ 18.

Use

import { convertJsonText, convertJsonValue } from "@rajnandan1/json-to-md";

convertJsonText('{ "hello": "world" }'); // untrusted serialized JSON text<br>convertJsonValue({ hello: "world" }); // already-parsed, caller-trusted data<br>// # Results<br>//<br>// ## hello<br>//<br>// world

CommonJS works too: const { convertJsonText } = require("@rajnandan1/json-to-md"). See the API reference for the two entry points’ exact contracts and errors.

Browser

Install

The same package: bundle it (Vite, webpack, esbuild; it’s plain ESM with no dependencies), or skip the build step entirely. Every npm release lands on the CDNs, and the build is a single self-contained ES module, so the raw file imports directly:

type="module"><br>import { convertJsonText } from "https://cdn.jsdelivr.net/npm/@rajnandan1/json-to-md@1/dist/index.js";<br>document.body.textContent = convertJsonText('{"hello":"world"}');

(@1 floats on the latest 1.x; pin @1.0.1 for exact bytes. The same path works on unpkg at https://unpkg.com/@rajnandan1/json-to-md@1.0.1/dist/index.js, or via esm.sh as https://esm.sh/@rajnandan1/json-to-md.)

No modules? A classic script tag works too; the IIFE build exposes a jsonToMd global. Pin it with Subresource Integrity so a compromised CDN can’t swap the code (the hash is per-version, so update both together when bumping):

src="https://cdn.jsdelivr.net/npm/@rajnandan1/json-to-md@1.0.1/dist/index.global.js"<br>integrity="sha384-v5+v71wtLTKJo2nT4Ly2Ov4u1QDPPPC0fhwy1q9I8lqsEtDOIuH/4AIwyt9lRQmR"<br>crossorigin="anonymous"

document.body.textContent = jsonToMd.convertJsonText('{"hello":"world"}');

Use

Identical API to Node; the conversion core is pure and runs anywhere. A live playground lives in demo/, a fully static page that loads the released library from the CDN, so any static file server works:

pnpm demo # serves it via vite and prints a localhost URL

Paste JSON, watch it convert, toggle between the rendered preview and raw Markdown, and switch the input between the serialized and parsed entry points to see numeric spelling preserved or lost.

Go

Install

go get github.com/rajnandan1/json-to-md/go

Use

import (<br>"errors"

jsontomd "github.com/rajnandan1/json-to-md/go"

md, err := jsontomd.ConvertText([]byte(`{"hello":"world"}`)) // byte-identical to convertJsonText<br>md, err = jsontomd.ConvertValue(v) // any Go value, marshal-then-convert

var convErr *jsontomd.Error<br>if errors.As(err, &convErr) {<br>// convErr.Code, .Pointer, .Location: same codes and UTF-16 locations as the TS errors

ConvertText preserves member order and numeric lexemes just like convertJsonText. ConvertValue is defined as json.Marshal(v) piped into the same core: struct fields render in field order, map keys in sorted order, and the core...

json markdown rajnandan1 convertjsontext document hello

Related Articles