Skip to content

Instantly share code, notes, and snippets.

@derekxmartin
Created September 16, 2026 15:43
Show Gist options
  • Select an option

  • Save derekxmartin/2d72e269a11bb7bc0eda1f783be21ee2 to your computer and use it in GitHub Desktop.

Select an option

Save derekxmartin/2d72e269a11bb7bc0eda1f783be21ee2 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""Extract required fields from Jira createmeta JSON. Prints to STDOUT.
Usage:
python3 required_fields.py < createmeta.json
python3 required_fields.py createmeta.json
curl ... | python3 required_fields.py
"""
from __future__ import annotations
import json
import sys
from typing import Any, Iterable
def _load(src: Any) -> Any:
data = json.load(src)
if isinstance(data, str):
data = json.loads(data)
return data
def _as_field_items(fields: Any) -> Iterable[tuple[str, dict]]:
if isinstance(fields, dict):
for key, meta in fields.items():
if isinstance(meta, dict):
yield key, meta
return
if isinstance(fields, list):
for meta in fields:
if not isinstance(meta, dict):
continue
key = meta.get("fieldId") or meta.get("key") or meta.get("id")
if key:
yield str(key), meta
def _collect_from_fields(fields: Any, out: list[dict]) -> None:
for field_id, meta in _as_field_items(fields):
if not meta.get("required"):
continue
out.append(
{
"id": field_id,
"name": meta.get("name") or field_id,
"hasDefaultValue": bool(meta.get("hasDefaultValue")),
"schema": (meta.get("schema") or {}).get("type"),
"custom": (meta.get("schema") or {}).get("custom"),
"allowedValues": [
v.get("name") or v.get("value") or v.get("id")
for v in (meta.get("allowedValues") or [])
if isinstance(v, dict)
],
}
)
def extract_required(data: Any) -> list[dict]:
required: list[dict] = []
# Shape A: GET /issue/createmeta/{project}/issuetypes/{issueTypeId}
# { "values": [ { "fieldId": "summary", "required": true, ... }, ... ] }
if isinstance(data, dict) and isinstance(data.get("values"), list):
sample = data["values"][0] if data["values"] else {}
if isinstance(sample, dict) and (
"fieldId" in sample or "required" in sample or "schema" in sample
):
_collect_from_fields(data["values"], required)
return required
# Shape B: classic createmeta
# { "projects": [ { "issuetypes": [ { "fields": { "summary": {...} } } ] } ] }
if isinstance(data, dict) and isinstance(data.get("projects"), list):
for project in data["projects"]:
for itype in project.get("issuetypes") or []:
_collect_from_fields(itype.get("fields"), required)
return required
# Shape C: a single issue type object with "fields"
if isinstance(data, dict) and "fields" in data:
_collect_from_fields(data.get("fields"), required)
return required
# Shape D: raw fields dict / list
if isinstance(data, (dict, list)):
_collect_from_fields(data, required)
return required
def main() -> int:
try:
if len(sys.argv) > 1 and sys.argv[1] != "-":
with open(sys.argv[1], encoding="utf-8") as fh:
data = _load(fh)
else:
if sys.stdin.isatty():
print("Pass JSON on stdin or as a file argument.", file=sys.stderr)
return 2
data = _load(sys.stdin)
except (OSError, json.JSONDecodeError) as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
required = extract_required(data)
if not required:
print("No required fields found.", file=sys.stderr)
return 0
for field in required:
extras = []
if field["hasDefaultValue"]:
extras.append("default")
if field["schema"]:
extras.append(field["schema"])
if field["custom"]:
extras.append(field["custom"].rsplit(":", 1)[-1])
suffix = f" ({', '.join(extras)})" if extras else ""
print(f"{field['id']}\t{field['name']}{suffix}")
if field["allowedValues"]:
print(f" allowed: {', '.join(str(v) for v in field['allowedValues'][:20])}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment