Loose JSON Converter

Parse {key=value} brace structures into JSON

Loading tool…

About Loose JSON Converter

Loose JSON Converter turns the pseudo-JSON that Java prints into real JSON. Paste a {key=value, nested={...}, list=[...]} string of the kind Map.toString() and object toString() produce, and it returns an indented, valid JSON document with numbers, booleans and nulls restored to their proper types.

Map.toString() is an output format, not a serialisation format, and it throws information away. Keys and values are unquoted, so a value containing a comma is indistinguishable from two values; braces serve both nested objects and message-template placeholders; a Java null prints as the four letters null, exactly like a string containing them. Recovering structure is therefore reconstruction under ambiguity, and the converter resolves the central ambiguity with one documented rule: a scalar stops at the next key= separator only until it has absorbed an = of its own, after which it is treated as free-form text and keeps every comma and brace up to the brace that closes its object.

How to use this developer tool

  1. Paste the string into the input pane. A surrounding pair of quotes from a log line is fine — one layer is stripped and the usual backslash escapes, including \uXXXX, are decoded first.
  2. Leave "convert numbers and booleans" on for JSON numbers, booleans and null, or switch it off to keep every scalar as a string when exact text matters more than type.
  3. Convert, and read the output pane. Indentation makes the recovered nesting obvious, and a value that swallowed more than it should is immediately visible as an unusually long string.
  4. Check any value that contains an = against the original line. That is the one place the documented heuristic makes a judgement call on your behalf.
  5. Copy the result, then parse it with the consumer that will actually use it before you build anything on top of the structure.

Worked example: an alerting rule out of a log line

A service configuration printed by Map.toString(). It mixes clean scalars, a list, a nested object and a notification template whose braces and commas are text, not structure.

Input
{name=order-service, port=8080, active=true, tags=[web, api], retry={max=3, delayMs=500}, summary=status={Status}, host={Host}, up={UpMs}ms}
JSON
{
  "name": "order-service",
  "port": 8080,
  "active": true,
  "tags": [
    "web",
    "api"
  ],
  "retry": {
    "max": 3,
    "delayMs": 500
  },
  "summary": "status={Status}, host={Host}, up={UpMs}ms"
}

The interesting field is summary. Its value contains host= and up=, which look exactly like the start of two more keys, and a naive split on ", " would produce three junk entries. Because the value had already taken an = of its own at status=, it is treated as free text and keeps everything through to the closing brace. retry and tags are structured values, matched by brace and bracket balance, so the heuristic never touches them.

What each fragment becomes

Coercion is deliberately conservative: a number is converted only when the JSON number round-trips to exactly the same text. Anything that would change on the way back stays a string.

Input fragmentCoercion on (default)Coercion off
port=80808080 (number)"8080"
active=truetrue (boolean)"true"
ca=nullnull"null"
id=007"007" — a leading zero would be lost"007"
ratio=1.50"1.50" — 1.5 is not the same text"1.50"
big=9007199254740993"9007199254740993" — beyond exact double precision"9007199254740993"
retries=-3-3 (number)"-3"
note=""""
tags=[web, api]["web", "api"]["web", "api"]
retry={max=3}{"max": 3}{"max": "3"}
expression=SuccessRate < 60"SuccessRate < 60" — no =, so it splits normallysame
msg=a=1, b=2"a=1, b=2" — one value, not twosame

Troubleshooting

Lombok @ToString output is returned as one long string.

Why it happens Lombok, and a Java record's generated toString, print ClassName(field=value, ...) with parentheses. The parser is written for the brace form that Map.toString() produces and only starts an object at a {.

Fix Replace the leading ClassName( with { and the matching trailing ) with }, and do the same for every nested ClassName( inside. The body between them already uses the field=value, shape the parser expects, so that is the only edit needed.

One value swallowed the keys that came after it.

Why it happens The free-text rule. A value containing an = keeps everything up to its object's closing brace, and a URL with a query string triggers it immediately: in {url=https://api.example.com/v1/jobs?state=failed, method=GET, timeoutMs=3000} the state= makes url free text, so method and timeoutMs end up inside it.

Fix Move the offending field to the end of its object before converting, so there is nothing after it to absorb. No local rule can tell a real key from a fragment of text, which is why the behaviour is documented rather than clever.

A number came out as a string.

Why it happens That is the conservative rule working. 007, 1.50 and 9007199254740993 all fail a round-trip check: they would come back as 7, 1.5 and 9007199254740992 respectively, so the converter keeps the original text.

Fix Nothing, if you want the value intact — a zero-padded order number or a 19-digit Snowflake id belongs in a string anyway. If you genuinely need a number, cast it in the consuming code where you know what the field means.

Conversion fails with a position instead of producing output.

Why it happens The parser reached something it cannot interpret at that offset: an entry with no =, a brace or bracket that never closes, or — most often with log data — a line that a logging framework truncated in the middle.

Fix Look at the reported offset and count braces from there. Truncated lines usually need the missing closing braces appended by hand; raise the logger's field-length limit if you have to do it more than once.

When this tool is the right choice

  • Making an alerting or Spring configuration that was only ever printed to a log readable and searchable.
  • Recovering a structured payload from a stack trace or a support ticket where nobody kept the original JSON.
  • Turning a Map.toString() dump into a JSON fixture you can paste into a test.
  • Checking quickly which fields a printed object actually carried, and which are absent rather than empty.

When to use something else

  • Formatting or validating real JSON. If the document has quoted keys and colons, use the JSON Formatter & Validator, which parses strictly and reports exact error offsets.
  • Parsing JSON5, HJSON or JSONC. Those are specified languages with their own grammars; this converter targets an unspecified debug-print format and uses heuristics that would be wrong for them.
  • Round-tripping data back into Java. The output is a best-effort reconstruction, so use it to read and to debug, not as a wire format between services.

Privacy and data handling

Parsing runs entirely in this browser tab; the string is never uploaded and nothing is stored between visits. These strings come from alerting configuration and application logs, which means they routinely carry webhook URLs with embedded tokens, internal hostnames and user identifiers. Redact those before pasting, and clear the input when you are done.

Related developer tools

Frequently asked questions

Why does a message value keep commas and braces that look like keys?

Because a scalar that has already absorbed one = is treated as free-form text and keeps everything up to the closing brace. That rule preserves a template such as summary=status={Status}, host={Host} instead of shredding it into separate keys, while clean values still split normally.

Does it handle Lombok @ToString?

Not directly. Lombok prints ClassName(a=1, b=2) with parentheses, and the parser expects braces. Swap the outer ClassName( ... ) for { ... }, along with any nested ones, and the rest converts as-is.

What does turning off coercion change?

Every scalar stays a string, including true, false, null and plain integers. Use it when comparing two printed objects textually, or when a consumer would be confused by a field that is sometimes a number and sometimes a string.

Can I trust the result enough to commit it?

Trust it for reading and debugging. Before committing it as a fixture or a configuration, re-read every value containing an = against the original line, because that is the only place the parser made a decision you did not.

Does it accept the quotes a log line wraps around the string?

Yes. One matching outer pair of single or double quotes is stripped, and standard escapes — \n, \t, \", \\ and \uXXXX — are decoded before parsing, which is exactly the shape you get when copying a field out of a JSON-formatted log record.

Why not write a proper grammar for this format?

Because there is no grammar to write. Map.toString() is documented as a display format with no escaping, so a value containing ", key=" is byte-for-byte identical to two entries. Any parser must guess; the useful thing a tool can do is guess the same way every time and tell you the rule.