Skip to content

Add JSON/XML config ingestion and rendering (#232)#279

Merged
jtdub merged 3 commits into
nextfrom
issue-232-structured-formats
Jul 19, 2026
Merged

Add JSON/XML config ingestion and rendering (#232)#279
jtdub merged 3 commits into
nextfrom
issue-232-structured-formats

Conversation

@jtdub

@jtdub jtdub commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Completes the remaining scope of #232 (set-style support and graceful XML/JSON rejection shipped in #278; this adds actual ingestion and rendering).

New hier_config/formats.py with a documented, invertible tree mapping:

  • HConfig.from_json(platform, data, list_keys=("name", "id")) — JSON objects/text (e.g. OpenConfig). Scalars become key <json> leaves, objects become branches, scalar lists become repeated leaves, and keyed lists (OpenConfig-style) become one branch per entry named by its identifying member — so entries diff structurally, exactly what the tree engine needs.
  • HConfig.from_xml(platform, source, list_keys=...) — XML documents (e.g. NETCONF payloads). Attributes map to @name leaves, text content to value leaves, repeated sibling elements are identified via list_keys, and mixed content uses #text leaves.
  • HConfig.to_json() / to_xml() — invert the mappings. Structured configs can be diffed with remediation(), predicted with future(), and the result rendered back in the source format. Round-trip fidelity (parse → render → reparse == original) is pinned by tests for both formats.
  • The format-detection error from v4: close out all remaining v4 issues #278 now points users at the new constructors.

Design notes:

  • Values are JSON-encoded inside leaf text (hostname "router1", port 123) so types survive the round trip.
  • xml.etree.ElementTree is safe here: it does not resolve external entities (XXE) — the S405 suppression is annotated.
  • Documented limits: a single-item JSON scalar list renders back as a bare scalar; NETCONF edit-config operation attributes have no remediation semantics yet (tracked as the final piece of v4: Graceful handling of structured config formats (XML, JSON, set-style) #232).

Test plan

  • 18 new unit tests: tree-shape assertions for both formats, round-trips (structural and reparse-equality), keyed lists incl. custom list_keys, error cases (invalid JSON/XML, non-object root, whitespace keys, unidentifiable list entries, multi-root XML), diff and future() over structured configs with JSON re-rendering, and detection-message updates.
  • poetry run ./scripts/build.py lint-and-test — all linters pass (ruff 0.15, mypy strict, pyright strict, pylint), 689 tests, ≥95% coverage.

🤖 Generated with Claude Code

New hier_config/formats.py maps structured configs onto the standard
HConfig tree and back:

- HConfig.from_json() ingests JSON objects or text (e.g. OpenConfig),
  with keyed lists identified via list_keys (default ("name", "id")).
- HConfig.from_xml() ingests XML documents (e.g. NETCONF payloads);
  attributes map to @name leaves, text content to value leaves, and
  repeated sibling elements are identified via list_keys.
- HConfig.to_json() / to_xml() invert the mappings, so structured
  configs can be diffed, predicted with future(), and rendered back in
  their source format. Round-trip fidelity is pinned by tests.
- The structured-format detection error now points at the new
  constructors instead of only suggesting CLI conversion.

Known limits (documented): a single-item JSON scalar list renders back
as a bare scalar, and NETCONF edit-config operation attributes carry no
remediation semantics yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jtdub and others added 2 commits July 18, 2026 19:07
An empty JSON object collapsed to null on to_json() because a
single-word childless node was read as a valueless leaf. from_json only
produces such nodes for empty objects (scalar leaves always carry a
value word, including `key null`), so they now invert to {}. Pinned by
a round-trip test alongside tests for the DuplicateChildError behavior
on duplicate list items/identities; the module docstring now documents
the empty-list drop and duplicate-item caveats.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from four parallel cleanup reviews applied:

- Promote driver resolution to a public registry.resolve_driver();
  constructors and formats share it instead of carrying private copies
  of the same Platform|str|driver dispatch.
- Type the JSON recursion with a recursive JsonValue alias, removing
  the annotated-assignment + pyright-ignore narrowing workarounds and
  the parsed-shadow variable.
- Single source for the ("name", "id") default: the HConfig
  classmethods take list_keys=None and formats resolves it against
  DEFAULT_LIST_KEYS.
- Merge _xml_children_into into _xml_element_into (one recursion, tag
  counting via collections.Counter) and classify children before
  splitting in _node_to_xml_element, removing the double text split
  per rendered node.
- Merge the duplicated "### Added" heading in the Unreleased changelog.
- Document that to_json()/to_xml() output is undefined for trees built
  by other constructors, and mark the @/#text line encoding as an
  implementation detail; drop issue tags from API docstrings.

Skipped per review guidance: single-scan _xml_identity_suffix (changes
list_keys priority semantics) and the _store_json_member double-lookup
micro-nit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jtdub
jtdub merged commit 7a48684 into next Jul 19, 2026
5 checks passed
@jtdub
jtdub deleted the issue-232-structured-formats branch July 19, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant