handlers/json.ps1

<#
.SYNOPSIS
    JSON Handler — schreibt Records in eine JSON-Datei.
 
YAML-Konfiguration:
    type: json
    path: "D:\Exports\{endpoint}\{datetime}.json"
    pretty: true # optional — eingerücktes JSON, default: false
    mode: array # optional — "array" (default) oder "lines" (JSONL)
    append: false # optional — nur für mode: lines sinnvoll
    fields: # optional — wenn leer: ganzer payload raw
      - field: id
        as: eventId
      - field: received_at
        as: receivedAt
      - field: payload.email
        as: email
      - field: "$"
        as: rawPayload
#>


param(
    [object[]]  $Records,
    [hashtable] $Config,
    [string]    $Endpoint,
    [string]    $ScriptDir
)

Set-StrictMode -Version Latest

# Konfiguration
$pretty  = if ($null -ne $Config['pretty'])  { [bool]$Config['pretty'] }  else { $false }
$mode    = if ($Config['mode'])              { $Config['mode'] }           else { 'array' }
$append  = if ($null -ne $Config['append'])  { [bool]$Config['append'] }  else { $false }
$fields  = $Config['fields']

# Pfad auflösen
$outPath = Resolve-PathTemplate -Template $Config['path'] -Endpoint $Endpoint -Target $Config['name']
$outDir  = Split-Path $outPath -Parent
$null    = New-Item -ItemType Directory -Path $outDir -Force

$results = [System.Collections.Generic.List[hashtable]]::new()

# Records in Objekte umwandeln
$objects = [System.Collections.Generic.List[object]]::new()

foreach ($record in $Records) {
    try {
        if ($fields -and $fields.Count -gt 0) {
            # Explizite Felder mappen
            $obj = [ordered]@{}
            foreach ($f in $fields) {
                $key = if ($f['as']) { $f['as'] } else { $f['field'] }
                $val = Resolve-Field -Record $record -FieldExpr $f['field']

                # Wenn Wert selbst JSON ist (payload/$), als Objekt einbetten
                if ($f['field'] -eq '$' -or $f['field'] -eq 'payload') {
                    try   { $val = $val | ConvertFrom-Json -ErrorAction Stop }
                    catch { <# als String belassen #> }
                }
                $obj[$key] = $val
            }
        } else {
            # Kein fields-Mapping — payload direkt als Objekt, plus Metadaten
            $payloadObj = $null
            try { $payloadObj = $record['payload'] | ConvertFrom-Json -ErrorAction Stop }
            catch { $payloadObj = $record['payload'] }

            $obj = [ordered]@{
                _id          = $record['id']
                _endpoint    = $record['endpoint']
                _source_ip   = $record['source_ip']
                _received_at = $record['received_at']
                payload      = $payloadObj
            }
        }

        $objects.Add($obj)
        $results.Add(@{ Id = $record['id']; Success = $true; Error = $null })
    } catch {
        $results.Add(@{ Id = $record['id']; Success = $false; Error = $_.Exception.Message })
    }
}

# Datei schreiben
$depth = 20  # ConvertTo-Json Tiefe
$writeMode = if ($append -and (Test-Path $outPath) -and $mode -eq 'lines') {
    [System.IO.FileMode]::Append
} else {
    [System.IO.FileMode]::Create
}

$stream = [System.IO.FileStream]::new($outPath, $writeMode, [System.IO.FileAccess]::Write)
$writer = [System.IO.StreamWriter]::new($stream, [System.Text.UTF8Encoding]::new($false))

try {
    if ($mode -eq 'lines') {
        # JSONL — eine Zeile pro Record
        foreach ($obj in $objects) {
            $json = $obj | ConvertTo-Json -Depth $depth -Compress:(-not $pretty)
            $writer.WriteLine($json)
        }
    } else {
        # Array-Modus
        $json = $objects | ConvertTo-Json -Depth $depth
        if (-not $pretty) {
            # Komprimieren: Whitespace entfernen (simpel für valides JSON)
            $json = $json | ConvertFrom-Json | ConvertTo-Json -Depth $depth -Compress
        }
        $writer.Write($json)
    }
} finally {
    $writer.Flush()
    $writer.Close()
    $stream.Close()
}

Write-Log "[$Endpoint][JSON] Geschrieben nach: $outPath ($mode)" -Level INFO
return $results