Functions/GenXdev.Helpers/Get-JsonExampleFromSchema.ps1
|
<############################################################################## Part of PowerShell module : GenXdev.Helpers Original cmdlet filename : Get-JsonExampleFromSchema.ps1 Original author : René Vaessen / GenXdev Version : 3.26.2026 ################################################################################ Copyright (c) 2026 René Vaessen / GenXdev Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ################################################################################> <############################################################################### .SYNOPSIS Generates a human-readable example JSON string from a JSON Schema definition. .DESCRIPTION Converts any JSON Schema (OpenAI json_schema format or raw schema) into a compact example JSON string suitable for injecting into LLM system instructions. Handles nested objects, arrays, scalar types, enums, unions, oneOf/anyOf/allOf compositions, and format hints like date-time and email. The function builds a PowerShell object tree by recursively walking the schema, then serializes it to a compact JSON string via ConvertTo-Json. .PARAMETER Schema A JSON Schema definition. Accepts either a JSON string or a pre-parsed PSCustomObject. Supports both the OpenAI json_schema wrapper format ({ json_schema: { schema: ... } }) and raw JSON Schema objects. .EXAMPLE $schema = '{ "type": "object", "properties": { "name": { "type": "string" } } }' Get-JsonExampleFromSchema -Schema $schema Generates: {"name":"..."} .EXAMPLE $openAiSchema = '{ "type": "json_schema", "json_schema": { "name": "response", "strict": true, "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "label": { "type": "string" } } } } } } } }' Get-JsonExampleFromSchema -Schema $openAiSchema Generates: {"items":[{"id":0,"label":"..."}]} #> ############################################################################### function Get-JsonExampleFromSchema { [CmdletBinding()] [OutputType([string])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseDeclaredVarsMoreThanAssignments', '')] param( ####################################################################### [Parameter( Mandatory = $false, Position = 0, HelpMessage = 'A JSON Schema definition (JSON string or ' + 'PSCustomObject). Supports OpenAI json_schema wrapper ' + 'format and raw JSON Schema objects.' )] [object] $Schema ####################################################################### ) begin { Microsoft.PowerShell.Utility\Write-Verbose ( 'Starting JSON Schema to example conversion.') # Recursively build an example PowerShell value from a schema node. # Returns a PS value: hashtable (object), array, string, int, bool, # or $null. function BuildExample($node, [int] $depth = 0) { # Guard against runaway recursion if ($depth -gt 10) { return '...' } # Unwrap PSObject to its base object if ($node -is [System.Management.Automation.PSObject]) { $node = $node.BaseObject } # Handle null / empty input if ($null -eq $node) { return @{ response = '...' } } # If it's a string, try to parse as JSON if ($node -is [string]) { $trimmed = $node.Trim() if ([string]::IsNullOrWhiteSpace($trimmed)) { return '...' } try { $node = $trimmed | Microsoft.PowerShell.Utility\ConvertFrom-Json } catch { return '...' } } # Auto-detect OpenAI json_schema wrapper and unwrap it if ($node.json_schema -and $node.json_schema.schema) { $node = $node.json_schema.schema } # ── Type Dispatch ────────────────────────────────────────── # 1. enum — use the first enum value if ($node.enum) { $enums = @($node.enum) if ($enums.Count -gt 0) { return $enums[0] } } # 2. const — use the const value directly if ($null -ne $node.const) { return $node.const } # 3. oneOf / anyOf — use the first schema option if ($node.oneOf) { $options = @($node.oneOf) if ($options.Count -gt 0) { return BuildExample $options[0] ($depth + 1) } } if ($node.anyOf) { $options = @($node.anyOf) if ($options.Count -gt 0) { return BuildExample $options[0] ($depth + 1) } } # 4. allOf — merge all sub-schemas into a single object if ($node.allOf) { $merged = [ordered]@{} foreach ($sub in @($node.allOf)) { $subResult = BuildExample $sub ($depth + 1) if ($subResult -is [hashtable]) { foreach ($key in $subResult.Keys) { $merged[$key] = $subResult[$key] } } } if ($merged.Count -gt 0) { return $merged } # Fall through if allOf produced nothing } # 5. Resolve the effective type, handling union type arrays # (e.g. ["string", "null"]) $effectiveType = $node.type if ($effectiveType -is [array] -or $effectiveType -is [object[]]) { # Pick the first non-null type foreach ($t in $effectiveType) { if ($t -ne 'null') { $effectiveType = "$t" break } } # If all were null, keep it as null if ($effectiveType -is [array]) { $effectiveType = 'null' } } # 6. object type (has properties or explicit type). # Use a helper to safely detect a real schema properties # collection, avoiding false positives from PSObject intrinsic # members when the node is a wrapped PSCustomObject. $hasRealProperties = _HasSchemaProperties $node if ($effectiveType -eq 'object' -or $hasRealProperties) { $result = [ordered]@{} if ($node.properties) { $props = $node.properties if ($props -is [System.Management.Automation.PSObject]) { $props = $props.PSObject.Properties } if ($props -is [System.Collections.IDictionary]) { foreach ($key in $props.Keys) { $result["$key"] = BuildExample $props[$key] ($depth + 1) } } else { foreach ($prop in $props) { $propName = if ($prop -is [System.Management.Automation.PSPropertyInfo]) { $prop.Name } else { $prop.Key } $propValue = if ($prop -is [System.Management.Automation.PSPropertyInfo]) { $prop.Value } else { $prop.Value } $result["$propName"] = BuildExample $propValue ($depth + 1) } } } return $result } # 7. array type if ($effectiveType -eq 'array') { # Comma prefix prevents PowerShell from unrolling the array # during function output. Without it, return @($x) unrolls # and the caller gets a single element, not an array. if ($node.items) { return , @(BuildExample $node.items ($depth + 1)) } return , @() } # 8. string type (with optional format hints) if ($effectiveType -eq 'string') { if ($node.format) { switch -Regex ($node.format) { '^date-time$' { return '2024-01-15T10:30:00Z' } '^date$' { return '2024-01-15' } '^email$' { return 'user@example.com' } '^uri$|^url$' { return 'https://example.com' } '^uuid$' { return '550e8400-e29b-41d4-a716-446655440000' } '^ipv4$' { return '192.168.1.1' } '^ipv6$' { return '::1' } '^hostname$' { return 'example.com' } '^json-pointer$' { return '/path/to/property' } '^regex$' { return '^[a-z]+$' } default { if ($node.description) { return "$($node.description)" } return '...' } } } if ($node.description) { return "$($node.description)" } return '...' } # 9. number / integer if ($effectiveType -eq 'number' -or $effectiveType -eq 'integer') { if ($node.description) { return "$($node.description)" } return 0 } # 10. boolean if ($effectiveType -eq 'boolean') { return $true } # 11. null if ($effectiveType -eq 'null') { return $null } # 12. Fallback — unknown or missing type, return a default object if ($node.description) { return "$($node.description)" } return @{ response = '...' } } # Helper: returns $true when $node has a real schema properties # collection (a Hashtable with keys), avoiding false positives # from PSObject intrinsic ".Properties" member on PSCustomObjects. function _HasSchemaProperties($node) { if ($null -eq $node) { return $false } if ($node -is [System.Management.Automation.PSObject]) { $node = $node.BaseObject } # Only dictionary types can have .properties as a key lookup if ($node -isnot [System.Collections.IDictionary]) { return $false } $props = $node['properties'] return ($props -is [System.Collections.IDictionary]) -and ($props.Keys.Count -gt 0) } # Serializes a schema node to JSON string and writes it to output. function ConvertAndEmit($schemaNode) { $exampleValue = BuildExample $schemaNode 0 if ($exampleValue -is [string]) { $json = '"' + ( $exampleValue -replace '\\', '\\\\' -replace '"', '\\"' ) + '"' } elseif ($null -eq $exampleValue) { $json = 'null' } elseif ($exampleValue -is [bool]) { $json = if ($exampleValue) { 'true' } else { 'false' } } elseif ($exampleValue -is [int] -or $exampleValue -is [long] -or $exampleValue -is [double]) { $json = [string] $exampleValue } else { # Hashtable, array, or PSCustomObject — use ConvertTo-Json # with -InputObject to preserve array structure. $json = Microsoft.PowerShell.Utility\ConvertTo-Json ` -InputObject $exampleValue ` -Depth 20 -Compress ` -WarningAction SilentlyContinue ` -ErrorAction SilentlyContinue if ([string]::IsNullOrWhiteSpace($json)) { $json = '{"response":"..."}' } } Microsoft.PowerShell.Utility\Write-Verbose ( "Generated schema example: $json") return $json } } end { if ($null -eq $Schema) { return '{"response":"..."}' } # Normalize: unwrap PSObject wrapping $schemaNode = $Schema if ($schemaNode -is [System.Management.Automation.PSObject]) { $base = $schemaNode.BaseObject if ($base -is [System.Collections.IDictionary]) { $schemaNode = $base } } try { ConvertAndEmit $schemaNode } catch { Microsoft.PowerShell.Utility\Write-Verbose ( "Schema example generation failed: $($_.Exception.Message)") '{"response":"..."}' } } } ############################################################################### |