Skip to content

Conversation

Copy link

Copilot AI commented Oct 29, 2025

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:

  1. Reorder the allOf items to put simpler properties before complex ones (with anyOf/oneOf)
  2. 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...


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits October 29, 2025 11:17
Co-authored-by: trieloff <39613+trieloff@users.noreply.github.com>
Co-authored-by: trieloff <39613+trieloff@users.noreply.github.com>
Co-authored-by: trieloff <39613+trieloff@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incorrect header hierarchy for nested properties in allOf with anyOf Fix header hierarchy for properties in allOf/anyOf composition schemas Oct 29, 2025
Copilot AI requested a review from trieloff October 29, 2025 11:37
@github-actions
Copy link

This PR will trigger no release when merged.

@codecov
Copy link

codecov bot commented Oct 29, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

- Refactor base level calculation into testable helper function
- Improve regex pattern by removing \d+ requirement
- Make test assertions more precise using regex patterns
- Add test coverage for oneOf constructs
- Add JSDoc documentation for calculateBaseLevel function

These improvements make the code more maintainable and thoroughly tested.

Signed-off-by: Lars Trieloff <lars@trieloff.net>
@trieloff trieloff marked this pull request as ready for review October 30, 2025 10:06
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.

Incorrect header hierarchy for nested properties in allOf with anyOf

3 participants