You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PowerShell has built-in ConvertFrom-Json / ConvertTo-Json for JSON, and the PSModule/Yaml module provides the same shape for YAML. There is no equivalent for TOML, even though TOML is widely used by package managers (Cargo.toml, pyproject.toml), config files, and CI tooling. Scripts that need to read or generate TOML today must shell out to external tooling or write ad-hoc string parsing.
Request
Desired capability
A ConvertFrom-Toml function that parses a TOML string into a PowerShell object, and a ConvertTo-Toml function that serializes a PowerShell object back to a TOML string. The module aligns with TOML v1.1.0 — the latest spec revision — and aims for full coverage of the spec.
ConvertFrom-Toml:
Accepts a valid TOML string via pipeline or -InputObject parameter
Returns a [PSCustomObject] by default (matches ConvertFrom-Json shape)
Supports -AsHashtable to return an [ordered] hashtable
Supports -NoEnumerate to prevent unwrapping single-element top-level arrays
Supports -Depth (default 1024) as a recursion safety net
Throws clear errors on invalid TOML (duplicate keys, mixed-type-table conflicts, malformed values) per the spec's "must produce an error" rules
ConvertTo-Toml:
Accepts PowerShell objects, hashtables, ordered dictionaries, and arrays via pipeline or -InputObject
Returns a TOML-formatted string
Supports -Depth (default 1024)
Supports -EnumsAsStrings to render enum values as their string names
Produces clean, idiomatic output with sensible header style — top-level keys then [table] headers (not always inline tables)
Aliases:ConvertFrom-Tml / ConvertTo-Tml registered via [Alias()], mirroring the Yml alias in PSModule/Yaml.
All examples from the TOML v1.1.0 spec parse correctly via ConvertFrom-Toml (verified via a spec test harness — see Implementation plan)
All TOML constructs the spec marks "INVALID" or "must produce an error" are rejected with a meaningful error
Reference compliance against the BurntSushi/toml-test compliance suite — both valid and invalid corpora pass
Note
Comment preservation through round-trip is out of scope for this initial issue and will be tracked separately, mirroring the equivalent decision in PSModule/Yaml.
Technical decisions
Spec alignment:TOML v1.1.0 (the published 1.1.0 spec). Rationale: latest stable, includes hex/octal/binary integers, mixed-type arrays, \e escape, multiline-string improvements over 1.0.0. The ABNF grammar is the formal reference.
Approach: Pure PowerShell implementation with no external dependencies, matching the PSModule/Yaml architecture. Hand-written recursive-descent parser driven by the ABNF.
Function placement: Public functions in src/functions/public/ — ConvertFrom-Toml.ps1 and ConvertTo-Toml.ps1. Internal helpers in src/functions/private/ (line tokenizer, value parser, key-path resolver, emitter helpers).
Output types:ConvertFrom-Toml returns [PSCustomObject] by default, [System.Collections.Specialized.OrderedDictionary] with -AsHashtable. ConvertTo-Toml returns [string].
Type mapping (parse):
TOML type
PowerShell type
String (all 4 forms)
[string]
Integer (dec/hex/oct/bin)
[long] (or [bigint] if overflow)
Float
[double] (incl. inf, -inf, nan)
Boolean
[bool]
Offset Date-Time
[datetimeoffset]
Local Date-Time
[datetime] with Kind=Unspecified
Local Date
[datetime] with Kind=Unspecified (time = 00:00)
Local Time
[timespan] (or a custom struct — see open question)
Array
[object[]]
Table / Inline table
[PSCustomObject] or [ordered]
Type mapping (emit): PowerShell types → TOML types, mirroring above. Strings choose between basic / multiline-basic / literal / multiline-literal automatically based on content (presence of newlines, double quotes, backslashes). Integers always emit as decimal by default; an -IntegerFormat option could be added later if needed (out of scope here).
Header style on emit: Prefer [section] headers for object-typed values where possible, fall back to inline tables only for nested objects inside arrays or where the path would otherwise be ambiguous. Top-level scalars/arrays come before any headers (TOML root-table rule).
Strictness: Strict by default. The TOML spec is explicit about what "must" produce errors (duplicate keys, redefining tables as arrays, etc.) — these are surfaced as terminating errors. No "lenient mode" for v1.
Test approach: Three layers:
Behavior tests — tests/ConvertFrom-Toml.Tests.ps1 and tests/ConvertTo-Toml.Tests.ps1 covering each spec section directly with hand-written cases.
Spec example harness — tests/Spec-1.1.0.Tests.ps1 containing every code example from the v1.1.0 spec page as a discrete It block, asserting against the JSON form the spec shows.
External compliance suite — optional CI step that runs the BurntSushi/toml-test corpus (valid + invalid directories) via thin adapters around the cmdlets. This catches edge cases the spec text doesn't enumerate.
Spec coverage checklist (TOML v1.1.0)
This is the scoreboard. Every box must be checked before this issue can close.
Cover every "INVALID" example from the spec with a Should -Throw assertion
Documentation
Update README — fill in NAME / DESCRIPTION, document TOML v1.1.0 alignment with link, add cmdlet usage examples mirroring the PSModule/Yaml README
Update examples/General.ps1 with TOML usage demos
Document the type-mapping table in README
Cleanup
Remove or repurpose the template scaffolding under src/functions/public/PSModule/ and src/functions/public/SomethingElse/ (placeholders from the module template)
Remove tests/PSModuleTest.Tests.ps1
Open questions
Local Time → PowerShell type: [timespan] represents duration, not time-of-day. Options: (a) use [timespan] and document the convention, (b) introduce a small [TomlLocalTime] struct, (c) use [datetime] with date pinned to 0001-01-01. Lean toward (a) for simplicity, but flag for review during implementation.
Big integers: should integers outside [long] range automatically promote to [bigint], or throw? Spec says "must throw an error" if a value cannot be represented losslessly. Lean toward throwing by default with an opt-in -AllowBigInteger switch (defer that switch to a follow-up).
-Compress / inline-table emission style: TOML doesn't have a JSON-style "compress" concept, but ConvertTo-Toml could expose -InlineTableThreshold or similar to force certain depths inline. Out of scope for v1.
PowerShell has built-in
ConvertFrom-Json/ConvertTo-Jsonfor JSON, and the PSModule/Yaml module provides the same shape for YAML. There is no equivalent for TOML, even though TOML is widely used by package managers (Cargo.toml,pyproject.toml), config files, and CI tooling. Scripts that need to read or generate TOML today must shell out to external tooling or write ad-hoc string parsing.Request
Desired capability
A
ConvertFrom-Tomlfunction that parses a TOML string into a PowerShell object, and aConvertTo-Tomlfunction that serializes a PowerShell object back to a TOML string. The module aligns with TOML v1.1.0 — the latest spec revision — and aims for full coverage of the spec.ConvertFrom-Toml:-InputObjectparameter[PSCustomObject]by default (matchesConvertFrom-Jsonshape)-AsHashtableto return an[ordered]hashtable-NoEnumerateto prevent unwrapping single-element top-level arrays-Depth(default 1024) as a recursion safety netConvertTo-Toml:-InputObject-Depth(default 1024)-EnumsAsStringsto render enum values as their string names[table]headers (not always inline tables)Aliases:
ConvertFrom-Tml/ConvertTo-Tmlregistered via[Alias()], mirroring theYmlalias in PSModule/Yaml.Acceptance criteria
$obj | ConvertTo-Toml | ConvertFrom-Tomlproduces an equivalent objectConvertFrom-Toml(verified via a spec test harness — see Implementation plan)validandinvalidcorpora passNote
Comment preservation through round-trip is out of scope for this initial issue and will be tracked separately, mirroring the equivalent decision in PSModule/Yaml.
Technical decisions
Spec alignment: TOML v1.1.0 (the published 1.1.0 spec). Rationale: latest stable, includes hex/octal/binary integers, mixed-type arrays,
\eescape, multiline-string improvements over 1.0.0. The ABNF grammar is the formal reference.Approach: Pure PowerShell implementation with no external dependencies, matching the PSModule/Yaml architecture. Hand-written recursive-descent parser driven by the ABNF.
Function placement: Public functions in
src/functions/public/—ConvertFrom-Toml.ps1andConvertTo-Toml.ps1. Internal helpers insrc/functions/private/(line tokenizer, value parser, key-path resolver, emitter helpers).Output types:
ConvertFrom-Tomlreturns[PSCustomObject]by default,[System.Collections.Specialized.OrderedDictionary]with-AsHashtable.ConvertTo-Tomlreturns[string].Type mapping (parse):
[string][long](or[bigint]if overflow)[double](incl.inf,-inf,nan)[bool][datetimeoffset][datetime]withKind=Unspecified[datetime]withKind=Unspecified(time = 00:00)[timespan](or a custom struct — see open question)[object[]][PSCustomObject]or[ordered]Type mapping (emit): PowerShell types → TOML types, mirroring above. Strings choose between basic / multiline-basic / literal / multiline-literal automatically based on content (presence of newlines, double quotes, backslashes). Integers always emit as decimal by default; an
-IntegerFormatoption could be added later if needed (out of scope here).Header style on emit: Prefer
[section]headers for object-typed values where possible, fall back to inline tables only for nested objects inside arrays or where the path would otherwise be ambiguous. Top-level scalars/arrays come before any headers (TOML root-table rule).Strictness: Strict by default. The TOML spec is explicit about what "must" produce errors (duplicate keys, redefining tables as arrays, etc.) — these are surfaced as terminating errors. No "lenient mode" for v1.
Test approach: Three layers:
tests/ConvertFrom-Toml.Tests.ps1andtests/ConvertTo-Toml.Tests.ps1covering each spec section directly with hand-written cases.tests/Spec-1.1.0.Tests.ps1containing every code example from the v1.1.0 spec page as a discreteItblock, asserting against the JSON form the spec shows.valid+invaliddirectories) via thin adapters around the cmdlets. This catches edge cases the spec text doesn't enumerate.Spec coverage checklist (TOML v1.1.0)
This is the scoreboard. Every box must be checked before this issue can close.
Lexical / structural
#comments (full-line and end-of-line)U+0000-U+0008,U+000A-U+001F,U+007Fin commentsKeys
A-Za-z0-9_-)1234 = "x") treated as stringsStrings
"...") with all listed escape sequences (\b \t \n \f \r \e \" \\ \xHH \uHHHH \UHHHHHHHH)"""...""")'...') — no escaping'''...''')Integer
+/-sign0xDEADBEEF(case-insensitive)0o7550b11010110+on hex/oct/binFloat
3.14)5e+22,1e06)6.626e-34).7,7.,3.e+20inf,+inf,-infnan,+nan,-nan[double]::PositiveInfinityetc. on emit+0.0/-0.0distinguished per IEEE 754Boolean
true/false(lowercase only)True/TRUE/Yesetc. as booleansDate / Time
Zand±HH:MM)07:32Z,07:32-07:00) → assume:001979-05-27)07:32:00,07:32)Array
Table
[table]header[a.b.c])[table]headers[header]for the same pathInline table
{ a = 1, b = 2 })[table]Array of tables
[[table]]header creates and appends elements[fruits.physical])[[fruits.varieties]])[table]conflicting with prior[[table]]on same path[[table]]conflicting with prior[table]on same pathOutput / emission
[section]s)Implementation plan
Core functions
ConvertFrom-Tomlinsrc/functions/public/ConvertFrom-Toml.ps1withConvertFrom-TmlaliasConvertTo-Tomlinsrc/functions/public/ConvertTo-Toml.ps1withConvertTo-TmlaliasGet-PSModuleTest/Set-PSModuleTest/Test-PSModuleTest/New-PSModuleTestscaffoldingTests
tests/ConvertFrom-Toml.Tests.ps1— behavior tests by spec section (one Describe per section above)tests/ConvertTo-Toml.Tests.ps1— emit + round-trip teststests/Spec-1.1.0.Tests.ps1— every example from the spec page as anItblockShould -ThrowassertionDocumentation
examples/General.ps1with TOML usage demosCleanup
src/functions/public/PSModule/andsrc/functions/public/SomethingElse/(placeholders from the module template)tests/PSModuleTest.Tests.ps1Open questions
[timespan]represents duration, not time-of-day. Options: (a) use[timespan]and document the convention, (b) introduce a small[TomlLocalTime]struct, (c) use[datetime]with date pinned to0001-01-01. Lean toward (a) for simplicity, but flag for review during implementation.[long]range automatically promote to[bigint], or throw? Spec says "must throw an error" if a value cannot be represented losslessly. Lean toward throwing by default with an opt-in-AllowBigIntegerswitch (defer that switch to a follow-up).-Compress/ inline-table emission style: TOML doesn't have a JSON-style "compress" concept, butConvertTo-Tomlcould expose-InlineTableThresholdor similar to force certain depths inline. Out of scope for v1.ConvertFrom-Yaml→ConvertTo-Yamlround-trip Yaml#28 once this lands.References