OpenViSTAI · Agents · Blockchain · Crypto
Menu
OPENVIST RECIPES

Audit parent references in JSON task records with Python

Python

Find roots and undeclared parent identifiers in a small task-record collection.

Python code excerpt: Audit parent references in JSON task records with Python. Version 0.1.0. Read the full recipe for code, sources and recorded test scope.

Find roots and undeclared parent identifiers in a small task-record collection. This is a local data check under the documented conventions; it does not execute tools or contact a service.

Before you start

  • Save the code as recipe.py and supply JSON through stdin to python3.12 recipe.py. No arguments are accepted, including --help.
  • Uses only json, sys and math. Reads stdin only; does not open paths, write files, run external commands or access a network.
  • Reads at most 65537 bytes and rejects input exceeding 65536 bytes. Decoding is strict UTF-8; a leading UTF-8 BOM, malformed JSON, nonstandard numeric constants and floating-point overflow are rejected before record processing.
  • Requires an array of at most 100 objects with unique IDs. Every object must contain id and parent. IDs and non-null parents must be strings containing 1 through 80 characters.
  • Null parents identify roots. Non-null parents absent from the complete ID set identify dangling references. Roots and dangling records are sorted by Python string order, with dangling records sorted by child ID. An empty array produces empty lists.
  • Successful processing exits 0 and writes one JSON object followed by a newline, with no stderr. Invalid arguments, invalid input and stdin read failures exit 2, produce no stdout and write only error: invalid input to stderr.

Build and check

  1. Use Python 3.12. Save the code as recipe.py.
  2. Save the supplied JSON example as input.json, encoded as UTF-8 without a BOM.
  3. Run: python3 recipe.py < input.json
  4. Compare the output with the expected result; handle exit code 2 as invalid input.

Limits to understand

  • String comparisons are exact and case-sensitive, without Unicode normalization. Lengths count Python string characters, not UTF-8 bytes. Escaped lone surrogates accepted by Python's JSON parser remain escaped in output.
  • Additional fields are ignored after parsing. Repeated JSON object member names use Python's default last-value-wins convention. There are no integer-valued schema fields; booleans cannot satisfy either identifier field.
  • All input and parsed records are held in memory. Parsing can consume substantially more memory than the byte limit, and interpreter recursion or resource failures are reported through the generic error path.
  • Stdin can block while waiting for data or EOF. There is no timeout.
  • Self-references are declared references and are not dangling. Cycles are not detected, and the output makes no claim of acyclicity.
  • Successful output contains task identifiers and parent identifiers and may be sensitive.
  • Output-write failures, including stdout write or flush failures, are outside the exit-status and diagnostic guarantees.

Run it

python3 recipe.py < input.json

Example input

[{"id": "a", "parent": null}, {"id": "b", "parent": "a"}, {"id": "c", "parent": "missing"}]

Expected output

{"roots": ["a"], "dangling": [{"id": "c", "parent": "missing"}]}
INSPECT THE SCRIPT

Inspect, copy and run the documented checks.

This browser preview builds code locally. It does not contact an API or run the code.

Default example ready.

Python example
import json
import sys
import math


MAX_BYTES = 65536


def reject_constant(_token):
    raise ValueError('invalid constant')


def parse_finite_float(token):
    value = float(token)
    if not math.isfinite(value):
        raise ValueError('nonfinite number')
    return value


def parse_integer(token):
    # Preserve large JSON integers without changing interpreter settings.
    negative = token.startswith('-')
    digits = token[1:] if negative else token
    value = 0
    for offset in range(0, len(digits), 1000):
        chunk = digits[offset:offset + 1000]
        value = value * (10 ** len(chunk)) + int(chunk)
    return -value if negative else value


def valid_identifier(value):
    return isinstance(value, str) and 1 <= len(value) <= 80


def process_input():
    if len(sys.argv) != 1:
        raise ValueError('arguments are not supported')

    raw = sys.stdin.buffer.read(MAX_BYTES + 1)
    if len(raw) > MAX_BYTES:
        raise ValueError('input too large')
    if raw.startswith(b'\xef\xbb\xbf'):
        raise ValueError('leading BOM')
    text = raw.decode('utf-8', errors='strict')

    records = json.loads(
        text,
        parse_constant=reject_constant,
        parse_float=parse_finite_float,
        parse_int=parse_integer,
    )
    if not isinstance(records, list) or len(records) > 100:
        raise ValueError('invalid collection')

    identifiers = set()
    pairs = []
    for record in records:
        if not isinstance(record, dict):
            raise ValueError('invalid record')
        if 'id' not in record or 'parent' not in record:
            raise ValueError('missing field')
        identifier = record['id']
        parent = record['parent']
        if not valid_identifier(identifier):
            raise ValueError('invalid identifier')
        if parent is not None and not valid_identifier(parent):
            raise ValueError('invalid parent')
        if identifier in identifiers:
            raise ValueError('duplicate identifier')
        identifiers.add(identifier)
        pairs.append((identifier, parent))

    roots = sorted(identifier for identifier, parent in pairs if parent is None)
    dangling = [
        {'id': identifier, 'parent': parent}
        for identifier, parent in sorted(pairs, key=lambda pair: pair[0])
        if parent is not None and parent not in identifiers
    ]
    return json.dumps(
        {'roots': roots, 'dangling': dangling},
        ensure_ascii=True,
        allow_nan=False,
        separators=(',', ':'),
    )


def main():
    try:
        output = process_input()
    except Exception:
        sys.stderr.write('error: invalid input\n')
        return 2

    # Output-write failures are deliberately outside the input-error handler.
    sys.stdout.write(output + '\n')
    sys.stdout.flush()
    return 0


if __name__ == '__main__':
    sys.exit(main())
What to verify after running
  • The supplied example must match the documented JSON output.
  • Error cases must exit 2 with no stdout. Review sensitive successful output before sharing it.

Request or format preview

Illustrative preview. No provider response, price or balance is fetched.

Change a setting to inspect the preview.

Official sources and related profiles

    Code license: MIT. Keep the license notice with your copy.

    Example version 0.1.0 · Documentation reviewed 2026-10-14.

    Six independent CLI fixtures and 10 curated supplementary checks passed in the pinned offline Python 3.12 container. This does not establish exhaustive correctness, production performance or compatibility with an external provider.