Skip to main content

clijson — router CLI output → JSON

CI Docs Python Typed uv Ruff License: MIT

📖 Documentation: https://shadymagdy.github.io/network-cli-parser/

clijson (repo: network-cli-parser) turns the output of show / display commands from Cisco IOS XR, Juniper Junos and Huawei VRP routers into clean, predictable JSON. It has no runtime dependencies.

>>> import clijson
>>> r = clijson.parse(open("pe1.log").read())        # platform and command are auto-detected
>>> r.parser, r.platform
('iosxr.show_bgp_summary', 'iosxr')
>>> r.data["neighbors"][0]
{'neighbor': '10.255.0.2', 'instance': 'default', 'vrf': 'default', 'address_family': 'ipv4 unicast',
 'remote_as': 65000, 'messages_received': 12011, ..., 'up_down': '1w2d', 'state': 'Established',
 'prefixes_received': 512}

Why clijson

Works on any command 173 dedicated parsers (251 command patterns). Anything else goes through a generic engine that finds tables, key: value pairs and indented sections in any output, so you always get JSON back.
Understands you like a router does sh ip int br, dis int br and show interfaces brief all resolve. Huawei accepts show as well as display. Parameters such as a VRF or interface are captured.
Zero configuration The platform is detected from the prompt, the command verb or fingerprints in the output. If those don't settle it, trial parsing lets each vendor's parser try and keeps the one that understands the output. Echoed prompts, --More-- pagers, ANSI codes, timestamps and {master} lines are removed.
One model for all vendors normalize=True adds a vendor-neutral view for 19 common concepts (BGP peers, interfaces, routes, LLDP, OSPF/IS-IS/LDP, ARP/ND, BFD, LAG, VRFs, L2VPN pseudowires, …), so one script can handle all three vendors.
Structured output is native Junos `
Config as data show running-config, show configuration (curly braces or `
Whole sessions Paste a terminal log with 20 commands and get 20 results (parse_session).
Pre/post change checks clijson.diff(before, after) (or clijson diff pre.txt post.txt) matches records by their natural key (neighbor, interface, prefix, …) and ignores counters and timers by default, so only real changes are reported.
Clear about failures Device errors (% Invalid input, syntax error, Error: Unrecognized command) come back as engine="device-error" with the message, instead of garbage data.
Stands on giants' shoulders If you have ntc-templates or Cisco Genie installed, their templates become extra fallback engines automatically.
Tells you how it got the answer Every result carries engine, parser, confidence, warnings (for example "output was filtered by `
Tested on real output 254 regression fixtures: 217 captured from real ASR9K, NCS5500, 8000, CRS, XRv, MX, PTX, QFX, EX, SRX, NE40E, CX600, ATN, CE, S and AR devices, plus 37 written from vendor documentation formats.
Easy to use from any language Python API, a full CLI (clijson), and a zero-dependency HTTP API (clijson serve).
Built for AI assistants clijson mcp is a Model Context Protocol server. Claude, Cursor, VS Code and any agent can call clijson as a tool and reason over exact, schema'd data.

What it looks like

Input: a Huawei capture pasted straight from the terminal, prompt included.

<PE1>display interface brief
PHY: Physical
*down: administratively down
Interface                   PHY   Protocol  InUti OutUti   inErrors  outErrors
Eth-Trunk1                  up    up        0.01%  0.38%          0          0
  GigabitEthernet0/0/1      up    up        0.01%  0.40%          0          0
  GigabitEthernet0/0/2      up    up           0%  0.36%          0          0
GigabitEthernet0/0/3        *down down         0%     0%          0          0
LoopBack0                   up    up(s)        0%     0%          0          0
<PE1>

clijson pe1.txt detects Huawei VRP and display interface brief, nests the trunk members and decodes the flags. Output is abbreviated here:

[
  {"interface": "Eth-Trunk1", "physical": "up", "protocol": "up", "input_utilization": 0.01, "output_utilization": 0.38,
   "input_errors": 0, "output_errors": 0,
   "members": [{"interface": "GigabitEthernet0/0/1", "physical": "up", "protocol": "up", ...},
               {"interface": "GigabitEthernet0/0/2", ...}]},
  {"interface": "GigabitEthernet0/0/3", "physical": "down", "protocol": "down", "admin_down": true, ...},
  {"interface": "LoopBack0", "physical": "up", "protocol": "up", "flags": ["spoofing"], ...}
]

clijson pe1.txt -n gives the vendor-neutral view. It has the same shape for IOS XR and Junos:

[{"name": "Eth-Trunk1", "admin_status": "up", "oper_status": "up", "ip_address": null, "vrf": null, "description": null},
 {"name": "GigabitEthernet0/0/3", "admin_status": "admin-down", "oper_status": "down", ...}, ...]

Install

pip install clijson                 # core, no dependencies
pip install "clijson[all]"          # + YAML output, pretty tables, ntc-templates fallback
pip install "clijson[netmiko]"      # + collect from live devices (or [scrapli])
pip install "clijson[mcp]"          # + MCP server for AI assistants

With uv:

uv add clijson                      # add to your project
uvx clijson parse show_bgp.txt      # run the CLI without installing anything

Requires Python 3.10 or newer.

Quick start

Python

import clijson

# 1. Tell it everything...
r = clijson.parse(output, "show ipv4 interface brief", platform="iosxr")

# 2. ...or nothing: prompt lines like "RP/0/RP0/CPU0:PE1#show ipv4 int br" are recognised
r = clijson.parse(output)

r.data                 # structured data (dict / list)
r.to_json()            # JSON string
r.to_yaml()            # needs PyYAML
r.to_dict()            # data + provenance (engine, parser, confidence, warnings, metadata)
r.records()            # the most table-like view as flat rows
r.to_dataframe()       # the same rows as a pandas DataFrame (needs pandas)

Pre/post maintenance check:

before = clijson.parse(pre_capture, "show bgp summary", "iosxr", normalize=True)
after = clijson.parse(post_capture, "show bgp summary", "iosxr", normalize=True)
for change in clijson.diff(before, after):
    print(change)
# ~ [neighbor=10.255.0.2].state: 'Established' -> 'Idle'
# - [neighbor=10.255.0.4]: {...}
# + [neighbor=10.255.0.5]: {...}

Same code for every vendor with the normalized view:

jobs = [("pe1-xr.txt",  "show bgp summary", "iosxr"),
        ("pe2-mx.txt",  "show bgp summary", "junos"),
        ("pe3-ne.txt",  "display bgp peer", "vrp")]

for path, cmd, platform in jobs:
    r = clijson.parse(open(path).read(), cmd, platform, normalize=True)
    for peer in r.normalized:
        if not peer["established"]:
            print(f"{path}: {peer['neighbor']} AS{peer['remote_as']} is {peer['state']}")

A whole terminal session (PuTTY/SecureCRT/script log):

for r in clijson.parse_session(open("maintenance-window.log").read()):
    print(r.metadata.get("hostname"), r.command, r.parser)

Live devices (via scrapli or netmiko). You can try it against containerlab XRd / cRPD / vJunos / VRP images:

from clijson.live import collect
results = collect("10.0.0.1", "junos", ["show version", "show bgp summary"],
                  username="lab", password="lab123", normalize=True)

Command line

clijson parse show_bgp.txt -p iosxr -c "show bgp summary"
ssh mx1 "show interfaces terse" | clijson parse -p junos -c "show interfaces terse"
clijson session.log                              # every command in a log (shorthand for `parse`)
clijson parse out.txt -c "dis bgp peer" -n -f table   # normalized, as a table
clijson parse out.txt -m                         # include engine/parser/confidence/warnings
clijson diff pre.txt post.txt -c "show bgp summary"   # what changed? (exit code 1 if anything did)
clijson commands -p vrp --search lldp            # what is supported?
clijson detect mystery.txt                       # which OS produced this?
clijson schema bgp.summary                       # JSON Schema of a normalized model
clijson run 10.0.0.1 -p iosxr -c "show version" -c "show bgp summary" -u admin
clijson serve --port 8080                        # HTTP API for other languages/tools
clijson mcp                                      # MCP server for AI assistants

HTTP API

clijson serve --port 8080 &
curl -s localhost:8080/parse -d '{"platform":"vrp","command":"display interface brief","output":"...","normalize":true}'
curl -s "localhost:8080/commands?platform=junos"

AI assistants (MCP)

claude mcp add clijson -- uvx --from "clijson[mcp]" clijson mcp     # Claude Code

For Claude Desktop, Cursor or VS Code, add the same command (uvx --from clijson[mcp] clijson mcp) to the client's MCP config. The assistant gets read-only tools: parse_output, parse_session, detect_platform, diff_outputs, list_commands, get_model_schema and check_pseudowire_redundancy. Setup for each client and the HTTP transport are covered in docs/mcp.md.

How it works

flowchart LR
    A[raw text] --> B[clean<br/>ANSI, pagers, CRLF,<br/>indentation]
    B --> C[prompt & echo<br/>extraction<br/>host, command, timestamp]
    C --> D{platform?}
    D -- given / prompt / verb / fingerprints --> E
    D -- still unknown --> T[trial parse<br/>every vendor]
    T --> E{output format}
    E -- JSON / XML --> S[structured engine]
    E -- text --> R[command grammar<br/>abbreviation-aware<br/>resolution]
    R --> N[native parser]
    N -. failed / missing .-> X[ntc-templates / Genie<br/>if installed]
    X -. missing .-> G[generic engine<br/>tables · key/value · sections]
    N --> M[normalize<br/>vendor-neutral model]
    S & N & X & G --> O[ParseResult<br/>data · engine · parser ·<br/>confidence · warnings]
  • Command grammar. Parsers declare what they understand using the notation from vendor documentation: show bgp [instance <instance>] [vrf (all|<vrf>)] [<afi> [<safi>]] summary. Typed commands are scored against every pattern. Exact keywords beat abbreviations and abbreviations beat parameters, so show interfaces brief never gets mistaken for show interfaces <interface>.
  • Engines are tried in order: native → ntc → genie → generic. Optional engines are skipped if they aren't installed. Use engines=[...] to choose your own order, or strict=True to accept only a dedicated parser.
  • Provenance. Each result has a confidence score: 1.0 for native and structured output, 0.9 for ntc/Genie, 0.4–0.6 for generic. Automation can decide how much to trust a result.

More detail: docs/architecture.md.

Supported platforms and commands

Platform Aliases (any of these work) Dedicated parsers
Cisco IOS XR (ASR9K, NCS 540/5500/5700, 8000, CRS, XRv 9000, XRd) iosxr, xr, cisco_xr, ios-xr, … 65
Juniper Junos / Junos Evolved (MX, PTX, ACX, QFX, EX, SRX, vMX, cRPD) junos, juniper, juniper_junos, evo, … 48
Huawei VRP (NE40E/NE8000, CX600, ATN, CE, S, AR) vrp, huawei, huawei_vrp, vrpv8, … 48

They cover the commands you run every day: version and inventory, platform and RE/FPC state, CPU, memory, power, fans and temperature, interfaces (brief, detail, description, counters, optics/DOM), LAG/LACP, IPv4/IPv6 addressing, ARP/ND and MAC tables, VLANs, LLDP/CDP, RIB (brief and detail/extensive), BGP (summary, neighbor detail, advertised/received routes and BGP RIB, including VRF, instance and address family), OSPF/OSPFv3, IS-IS, MPLS LDP, RSVP-TE tunnels and LSPs, LFIB, BFD, HSRP/VRRP, PIM, VRFs and VPN instances, L2VPN xconnects and bridge domains, EVPN, firewall filters, ACLs, SRX cluster and policies, NTP, users, alarms, logging, licenses, file systems, commit history, startup/patch info and running configuration.

The full, generated list is in docs/commands.md. You can also run clijson commands.

Normalized models

With normalize=True, commands that map to a common concept return records with exactly these fields on every vendor:

Model Fields
system.version hostname, vendor, os, version, model, serial_number, uptime, uptime_seconds
interfaces.brief name, admin_status, oper_status, ip_address, vrf, description
interfaces.detail name, admin_status, oper_status, description, mac_address, mtu, bandwidth_kbps, ipv4_addresses, input/output rate & packets & errors
bgp.summary neighbor, remote_as, state, established, uptime, uptime_seconds, prefixes_received, vrf, address_family
routes prefix, protocol, next_hops, distance, metric, vrf, age
lldp.neighbors local_interface, neighbor, neighbor_interface, chassis_id, capabilities, ttl
l2vpn.pseudowires service, neighbor, pw_id, state, role (primary/backup), active, vc_type, mtu, local_label, remote_label. See the pseudowire checks guide.
ospf.neighbors, isis.adjacency, ldp.neighbors, bfd.sessions, arp, ipv6.neighbors, mac.table, lag, vrfs, inventory, cpu, interfaces.description see docs/models.md

Normalized values are standardised too. Statuses become up / down / admin-down, MACs become aa:bb:cc:dd:ee:ff, and uptimes, ages and timers are also given in seconds.

Typed and schema'd. Every model is a TypedDict (clijson.models.BgpNeighbor, Route, Interface, ...), so editors autocomplete fields and mypy/pyright check your code. Every model also has a JSON Schema (draft 2020-12) for consumers in other languages, API contracts or data pipelines:

from typing import cast
from clijson.models import BgpNeighbor, json_schema, validate

peers = cast(list[BgpNeighbor], clijson.parse(text, "show bgp summary", "iosxr", normalize=True).normalized)
json_schema("bgp.summary")          # dict, ready for json.dump / OpenAPI / pydantic / jsonschema
validate("bgp.summary", peers)      # [] when the data matches the model
clijson schema                                  # list the models
clijson schema routes > routes.schema.json      # print one
clijson schema --out schemas/                   # export all of them
clijson parse out.txt -c "show arp" -n | clijson schema arp --check -   # validate output in CI

The generated schemas are also committed under schemas/.

Extending

Adding a parser takes a decorator and a method. Run python scripts/fixture.py add to add a regression test for it:

from typing import Any

from clijson import Parser, register
from clijson.textutils import match_lines

@register("iosxr", "show hsrp [<interface>] brief", intent=None)
class ShowHsrpBrief(Parser):
    """HSRP groups, state and virtual IP."""

    def parse(self, text: str) -> list[dict[str, Any]]:
        return [m.groupdict() for m in match_lines(
            r"^\s*(?P<interface>\S+)\s+(?P<group>\d+)\s+(?P<priority>\d+)\s+(?P<state>\w+)\s+(?P<vip>\S+)", text)]

Third-party packages can ship parsers through the clijson.parsers entry point. See docs/writing-parsers.md.

Testing against real routers

lab/ has a containerlab topology with Cisco XRd, Juniper vJunos/cRPD and Huawei VRP. scripts/harvest.py runs every supported command on each device (live or emulated), saves the raw output and reports how much of it parsed natively. This is how new OS releases get checked. See lab/README.md.

Development

The project is managed with uv (uv.lock pins every tool):

uv sync                                 # create .venv with the dev dependency group
uv run pre-commit install               # ruff lint + format on every commit
uv run pytest                           # ~1200 tests incl. 254 fixtures
uv run ruff check . && uv run ruff format .
uv run mypy                             # strict type check
uv run scripts/fixture.py check         # or `update` after an intentional parser change
uv run scripts/gen_docs.py              # refresh docs/commands.md, docs/models.md and schemas/
uv run --group docs zensical serve      # preview the documentation site

See CONTRIBUTING.md.

License

MIT. Some regression fixtures were taken from the Apache-2.0 licensed ntc-templates and genieparser projects. See tests/fixtures/NOTICE.md.

Metadata

Release files for clijson 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for clijson 0.3.1
File Size Uploaded
clijson-0.3.1.tar.gz 141.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clijson 0.3.1
File Interpreter ABI Platform
clijson-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 308.0 kB

Release files / clijson-0.3.1.tar.gz

Download URL clijson-0.3.1.tar.gz
Size 141.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1f6a0b08d2a1bbea80edb9f0b85f2a8ba1dcd18c3e00c5d3f5a9230f75470950
BLAKE2b-256 checksum
How to use checksums
540e4e7a59da00c5a1c16e15da333047c311d97e93046c9196230e107412c8d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / clijson-0.3.1-py3-none-any.whl

Download URL clijson-0.3.1-py3-none-any.whl
Size 166.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6f7861278a8a63865afbe5b622dccb3c4ecc8fcda944c778500eb47947c438e8
BLAKE2b-256 checksum
How to use checksums
19345f05c328f4e653cff3f8c0f38ca78b7f7febe3183fe3b1b42ff2eb9a1be2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page