paritytech / paritytech/polkadot-cli

`--json` outputs invalid JSON (`undefined`) for empty storage values

Open
#162 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
10
Forks
2
Avg merge
12h 35m
Merged PRs (30d)
4

Description

Problem

Storage queries and runtime API calls return the literal string undefined when there is no value — not null, not valid JSON. This breaks any jq or JSON-consuming pipeline.

$ dot asset-hub.apis.AssetConversionApi.get_reserves "$NATIVE" "$ASSET" --json
undefined
# Breaks with: jq: parse error: Invalid numeric literal
echo "$RESERVES" | jq '.[0]'

Impact

Every script needs a defensive guard before any jq call:

if [ "$RESERVES" == "undefined" ] || [ "$RESERVES" == "null" ]; then ...

Three-Way Semantics

There are three distinct states in Substrate storage, and dot correctly distinguishes them:

Substrate state dot output Example
Key exists, has value {...} / "..." Assets.Account for funded account
Key exists, value is None null Game.Nfts entry (key present, Option is None)
Key doesn't exist at all undefined Assets.Account for unknown address

Collapsing undefined into null would lose the distinction between "key not found" and "key exists but maps to None" — which matters for pallets where null values are meaningful.

Root Cause

formatJson() in src/core/output.ts calls JSON.stringify(data, replacer, 2). When data is JS undefined, JSON.stringify(undefined) returns the JS value undefined (not a string), and console.log(undefined) prints the literal string "undefined" to stdout — which is not valid JSON.

Additionally, formatPretty() has an explicit fallback that returns the string "undefined".

Suggested Fix

Keep the three-way semantics, but make --json output always valid JSON. Options:

Option A: Envelope

Wrap in a result object when --json is passed:

{"found": true, "value": {"balance": "1000"}}   // key exists with value
{"found": true, "value": null}                    // key exists, value is None
{"found": false}                                  // key doesn't exist

Most explicit. Self-documenting. No ambiguity. But changes the JSON shape for existing consumers.

Option B: Exit code + null

Return null for both absent cases under --json, but use exit code to distinguish (0 = found, 1 = not found). Scripts that don't care about the distinction just check for null. Scripts that do can check $?.

Option C: Empty stdout + exit code

Keep undefined for human-readable output. Under --json, emit nothing to stdout and set a non-zero exit code for "not found". This lets scripts do:

if RESERVES=$(dot ... --json 2>/dev/null); then
  echo "$RESERVES" | jq ...
else
  echo "not found"
fi

Most unix-y (how grep works). But non-zero exit codes can trip set -e scripts, and both "None" and "not found" would look the same to JSON consumers.

Key Files

  • src/core/output.tsformatJson() and formatPretty() functions

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading src/core/output.ts, focusing on formatJson() and formatPretty(), then trace how --json handles missing storage values. Done means choosing and documenting one of the proposed semantics, preserving the distinction between absent keys and None values, and ensuring JSON-mode output is always valid JSON.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
cli
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.