Fix header hierarchy for properties in allOf/anyOf composition schemas
Properties defined within allOf/anyOf/oneOf composition constructs were rendered with incorrect header levels, breaking document hierarchy when schemas contain nested composition patterns.
Problem
For a schema like:
{
"properties": {
"docker": {
"allOf": [
{
"anyOf": [
{ "properties": { "testSteps": {...} } }
]
},
{ "properties": { "preTestSteps": {...} } }
]
}
}
}
Property detail sections for testSteps and preTestSteps used base level 1, resulting in:
-
# Properties(level 1) -
## propertyName(level 2)
But since these are nested within docker (which itself is at level 3), they should use higher levels to maintain hierarchy.
Changes
Header level calculation (lib/markdownBuilder.js)
- Detect composition schemas by checking for
/allOf/,/anyOf/, or/oneOf/in JSON pointer path - Calculate property depth from number of
/properties/segments - Use property depth as base level for composition schemas (regular schemas remain at level 1)
Result: Composition schema properties now render as:
-
## Properties(level 2) -
### propertyName(level 3) -
#### Type(level 4)
This preserves hierarchy when documentation is assembled or viewed in context.
Testing
Added test fixture with nested allOf/anyOf structure and 3 test cases validating correct header levels for composition schemas vs regular properties.
Original prompt
This section details on the original issue you should resolve
<issue_title>Incorrect header hierarchy for nested properties in allOf with anyOf</issue_title> <issue_description>
Description
When using allOf with multiple schemas where one contains an anyOf, the generated markdown documentation has incorrect header hierarchy for the property detail sections. Specifically, properties that are defined in separate allOf items appear with headers at the wrong level.
Expected Behavior
For a schema structure like:
# Example Schema (level 1)
## tests (level 2)
### tests.docker (level 3)
#### Option 1: testScript (level 4)
#### Option 2: testSteps (level 4)
#### preTestSteps (level 4)
All detail sections under tests.docker should be at level 4 (####) since they're nested under a level 3 header (###).
Actual Behavior
The generated markdown has inconsistent header levels:
# Example Schema (level 1)
## tests (level 2)
### tests.docker (level 3)
Option 1: testScript (no header - inline)
Option 2: testSteps (no header - inline)
## Option 2]: testSteps[]: Test Steps (level 2 ❌ should be level 4)
### 1.preTestSteps[]: Pre-test Steps (level 3 ❌ should be level 4)
The detail sections for testSteps and preTestSteps are promoted to level 2 and 3 respectively, breaking the document hierarchy.
Minimal Reproduction
See also: jsonschema2md-hirachie-test.zip
Input Schema (schema.json):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Example Schema",
"type": "object",
"properties": {
"tests": {
"title": "Tests Configuration",
"description": "Configure test execution",
"type": "object",
"properties": {
"docker": {
"title": "Docker Tests",
"description": "Run Docker-based tests",
"type": "object",
"allOf": [
{
"title": "test-options",
"description": "Test execution options",
"anyOf": [
{
"title": "script",
"properties": {
"testScript": {
"title": "Test Script",
"description": "Script to execute for testing",
"type": "string"
}
},
"required": ["testScript"]
},
{
"title": "stepList",
"properties": {
"testSteps": {
"title": "Test Steps",
"description": "List of test steps to execute",
"type": "array",
"items": {
"type": "object"
}
}
},
"required": ["testSteps"]
}
]
},
{
"properties": {
"preTestSteps": {
"title": "Pre-test Steps",
"description": "Steps to execute before running tests",
"type": "array",
"items": {
"type": "object"
}
}
}
}
]
}
}
}
}
}
Code to generate markdown:
const jsonschema2mk = require("jsonschema2mk");
const fs = require("fs");
const schema = JSON.parse(fs.readFileSync("schema.json", "utf8"));
const jsm = new jsonschema2mk({
schema: undefined,
level: undefined,
extension: ["table-format-2", "yaml-examples"],
plugin: undefined,
partials: undefined,
});
const markdown = jsm.convert(schema);
fs.writeFileSync("output.md", markdown);
Problematic output (lines 90 and 107):
<a name="testsdocker"></a>
### tests\.docker: Docker Tests
Run Docker-based tests
**Option 1 (optional):**
**Properties**
...
<a name="option2teststeps"></a>
## Option 2\]: testSteps\[\]: Test Steps 👈 Should be #### not ##
List of test steps to execute
<a name="1preteststeps"></a>
### 1\.preTestSteps\[\]: Pre\-test Steps 👈 Should be #### not ###
Steps to execute before running tests
Environment
- jsonschema2md version: 8,0,7
- Node.js version: v22.20.0
Workaround
As a temporary workaround, we:
- Reorder the
allOfitems to put simpler properties before complex ones (withanyOf/oneOf) - Post-process the generated markdown with regex replacements to fix header levels
Impact
This breaks the logical document structure and makes navigation difficult, especially in documentation systems that rely on proper heading hierarchy for table of contents generation. Thi sis a simplified example and in complex documents it gets unreadable. </issue_description>
<agent_instructions>reproduce the issue in a test first, then fix the issu...
- Fixes adobe/jsonschema2md#678
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.
This PR will trigger no release when merged.
Codecov Report
:white_check_mark: All modified and coverable lines are covered by tests.
:loudspeaker: Thoughts on this report? Let us know!