Convert Nmap XML output to JSON.
nmap2json can be used as a Python library or as a command-line tool. It reads
Nmap XML (-oX) and returns one JSON array containing one object per host.
- Python 3.10+
From this repository:
python -m pip install .After updating this checkout, reinstall it in the environment used by your scanner or migration tools (updating Git alone does not update that environment):
python -m pip install --force-reinstall --no-deps .
python -c "import nmap2json.smarthash as s; print(s.__file__)"The package metadata currently uses version 0.0.0; record the deployed Git
revision with git rev-parse HEAD rather than relying on the package version
alone. Installing this checkout does not publish a release to PyPI.
Generate an Nmap XML report:
nmap -v -A -oX myoutput.xml -p 25,80,443,22 -Pn www.example.orgPrint converted JSON to stdout:
python -m nmap2json -i myoutput.xmlWrite JSON to a file:
python -m nmap2json -i myoutput.xml -o output.jsonWrite one JSON file per host, prefixed with the host IP:
python -m nmap2json -i myoutput.xml -o output.json --multipleFilter output:
python -m nmap2json -i myoutput.xml --notopen
python -m nmap2json -i myoutput.xml --deadhostusage: python3 -m nmap2json [-h] -i INPUT [-o OUTPUT] [-m] [-n] [-d] [--debug]
Convert Nmap XML to JSON
options:
-h, --help show this help message and exit
-i, --input INPUT Input Nmap XML file
-o, --output OUTPUT Output JSON file (prints to stdout if omitted)
-m, --multiple Enable multiple JSON outputs (IP prefixed)
-n, --notopen Remove from output closed ports
-d, --deadhost Remove from output dead hosts
--debug Export with smarthash masking
Note: --debug is currently parsed but not applied by the CLI. It does not
export masked data. Use master_clean() below to inspect normalization.
Each host object includes extra fields:
{
"host_reply": true,
"hsh256": "446c094a24f248da6a87cc7bffaae3df9cf5b0dc5a07d1ca7fff8cdb2071b389"
}host_reply:truewhen at least one scanned port is open.hsh256: stable SHA-256 hash of the host object, excludingstarttime,endtime, and existing hash fields.
Each port also gets its own hsh256 field.
Smart hashing masks volatile protocol dates with a fixed [DATE] marker in
the hash input: SMTP 220 ...; <date> greetings, HTTP/RTSP Date: headers in
banner, and Date: headers in http-headers / http-security-headers
(plus cookie expiry). Textual weekday/month dates support case variations, one- or
two-digit days, numeric timezones, and month-first RTSP dates. Banner line
breaks may be literal or Nmap-escaped. This is deliberately not a generic
date remover: certificate validity, Last-Modified, and build dates remain
significant. Other date formats are not currently normalized.
Normalization leaves original reports unchanged. SHA-256 and the existing JSON serialization remain unchanged. Previously stored hashes are not updated automatically: rehashing old reports requires a separate migration, including merging observation timestamps when multiple old IDs collapse to one new ID.
Load Nmap XML from a string:
import json
from nmap2json import nmap_xml_to_json
python_obj = nmap_xml_to_json(xml_str)
print(json.dumps(python_obj, indent=2))Load Nmap XML from a file:
import json
from nmap2json import nmap_file_to_json
python_obj = nmap_file_to_json("myoutput.xml")
print(json.dumps(python_obj, indent=2))Filter closed ports or dead hosts from library calls:
from nmap2json import nmap_file_to_json
only_open_ports = nmap_file_to_json("myoutput.xml", wipe_notopen=True)
only_live_hosts = nmap_file_to_json("myoutput.xml", wipe_deadhost=True)from copy import deepcopy
from nmap2json.smarthash import port_smart_hash
port = {
"protocol": "tcp",
"portid": "25",
"scripts": [{
"id": "banner",
"output": "220 mail ESMTP; mon, 2 mar 2026 10:13:52 -0500",
}],
}
later = deepcopy(port)
later["scripts"][0]["output"] = (
"220 mail ESMTP; Tue, 12 May 2026 10:15:46 GMT"
)
assert port_smart_hash(port) == port_smart_hash(later)
assert "mon, 2 mar" in port["scripts"][0]["output"] # Raw data unchanged.port_smart_hash() returns a lowercase SHA-256 hexadecimal string and excludes
existing hsh256 fields by default. For a complete host object, call
headers_smart_hash(host, exclude_keys=["starttime", "endtime", "hsh256"]).
Unlike the converter, a direct call does not sort input dictionaries/lists:
keep serialization order consistent when rehashing stored reports.
To inspect a normalized copy without hashing:
from nmap2json.smarthash import master_clean, SMART_HASH_SCRIPTS
normalized = master_clean({"ports": [port]}, SMART_HASH_SCRIPTS)
print(normalized["ports"][0]["scripts"][0]["output"])
# 220 mail ESMTP; [DATE]The improved normalization changes hashes for affected reports. Deploy the same revision on scanners and migration tools. A plain export/import that retains old IDs cannot deduplicate old date variants: recalculate port hashes and derived IDs, merge earliest/latest observation bounds, and remove superseded IDs during replacement. Preserve original report contents; select a deterministic representative when several reports merge. nmap2json itself does not migrate databases or rewrite historical IDs.
From the repository root:
PYTHONPATH=src python -m unittest discover -s tests -vDate regression tests cover SMTP, HTTP/RTSP, escaped banner line endings,
header/cookie clocks, raw-input preservation, and meaningful differences
(service versions, certificate validity, Last-Modified, build dates).
GNU Affero General Public License v3 or later.