Skip to content

Reconstruct a typed markdown object hierarchy from a deserialized graph #31

Description

The object hierarchy from #8 is designed to be handed to any general-purpose serializer, so a markdown document can be turned into YAML, JSON, or CLIXML with no markdown-specific code:

Get-Content -Raw 'README.md' | ConvertFrom-Markdown | ConvertTo-Yaml | Set-Content 'README.yaml'

Only one direction works. Coming back the other way produces untyped PSCustomObject graphs, not MarkdownNode instances, so ConvertTo-Markdown cannot render them and none of the traversal helpers exist on them.

Request

Desired capability

A way to read a serialized hierarchy back into typed nodes, closing the loop:

$doc = Get-Content -Raw 'README.yaml' | ConvertFrom-Yaml | ConvertTo-MarkdownDocument
$doc.Descendants('Heading') | ForEach-Object { $_.GetText() }
$doc | ConvertTo-Markdown | Set-Content 'README.md'

This makes the hierarchy a genuine interchange format rather than a one-way export. The scenarios it unlocks:

  • Edit markdown structure in a format that is easier to manipulate. Export to YAML, transform with any YAML tooling, come back to markdown.
  • Store parsed documents. Cache a parse result and reload it without re-parsing.
  • Generate markdown from data. Produce the hierarchy as YAML or JSON from a template or another system, then convert it to markdown without ever constructing PowerShell classes by hand.
  • Cross-process and cross-language hand-off. Any tool that can emit the documented schema can produce a markdown document.

Acceptance criteria

  • A public function reconstructs a typed MarkdownDocument from a deserialized object graph — the output of ConvertFrom-Yaml, ConvertFrom-Json, or Import-Clixml
  • Each node's Type property selects the class to instantiate
  • Nested Children are reconstructed recursively into typed nodes
  • Enum-valued properties are restored from their string names
  • ConvertFrom-Markdown | ConvertTo-Yaml | ConvertFrom-Yaml | <this function> | ConvertTo-Markdown reproduces the original document
  • An unrecognized Type, a missing required property, or a malformed graph produces a clear error naming the offending node — not a partially built tree
  • Source spans are restored when present and left $null when absent

Dependencies

  • PSModule/Markdown#8 — the object hierarchy, the Type discriminator, and the node constructors this function calls

Technical decisions

Open: function name. Candidates, none settled:

Candidate For Against
ConvertTo-MarkdownDocument Reads naturally in a pipeline; the noun says what comes out A second ConvertTo-* verb in a module that already has ConvertTo-Markdown invites confusion
ConvertFrom-MarkdownObject Pairs with ConvertFrom-Markdown The direction reads backwards — it converts to the hierarchy
New-MarkdownDocument Standard verb for constructing an object Suggests an empty document, not a rehydrated one
Import-MarkdownDocument Clear intent Import-* implies reading from a file, which this does not do

To be resolved before implementation.

Reconstruction is driven by Type: Every node carries a Type string precisely so the serialized form is self-describing. The function maps Type to a class and instantiates it. No type inference from property shape — an explicit discriminator or an error.

Strict by default: An unknown Type or a missing required property is an error, not a silently dropped node. Producing a tree that is quietly incomplete is worse than failing, because the failure surfaces as corrupted markdown much later.

Accepts any deserialized graph: The function takes PSCustomObject and hashtable shapes, so it works with ConvertFrom-Yaml, ConvertFrom-Json, and Import-Clixml alike without a format-specific parameter. The source format is the caller's concern.

The schema is the contract: The node schema documented in #8 becomes a public interface once external tools can produce it. Schema changes after this ships are breaking changes, which is an argument for landing it after 1.3 has settled rather than alongside it.

Release shape: A new public function. Additive, so a minor bump. Sequenced after 1.5, since the dialect node types from #30 should be reconstructable too rather than requiring a second pass over this function.


Implementation plan

Core

  • Settle the function name
  • Create the function in src/functions/public/
  • Build a Type to class map covering every node type in the hierarchy
  • Instantiate nodes via their constructors and populate properties from the deserialized graph
  • Reconstruct Children recursively
  • Restore enum-valued properties from their string names
  • Restore MarkdownSourceSpan when present
  • Accept PSCustomObject and hashtable node shapes

Error handling

  • Throw a clear error naming the node for an unrecognized Type
  • Throw a clear error naming the node and property for a missing required property
  • Never return a partially constructed tree

Tests

  • Round-trip through YAML for every example in the conformance suite
  • Round-trip through JSON for the same set
  • Test each node type individually
  • Test the error paths: unknown type, missing property, malformed graph

Documentation

  • Add comment-based help with examples
  • Document the round-trip through another serializer in README.md
  • Mark the node schema as a public interface, with the stability implications that carries

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions