Shared/Export-XRefData.ps1

using namespace System.Collections.Generic
#.ExternalHelp StreamXRef-help.xml
function Export-XRefData {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
    [OutputType([System.Void])]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("PSPath")]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-Path $_ -IsValid })]
        [string]$Path,

        [Parameter()]
        [Alias("NoKey", "EAK")]
        [switch]$ExcludeApiKey,

        [Parameter()]
        [Alias("NoMapping", "ECM")]
        [switch]$ExcludeClipMapping,

        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [switch]$NoClobber,

        [Parameter()]
        [switch]$Compress,

        [Parameter(DontShow)]
        [switch]$_PersistConfig
    )

    Begin {
        if ([string]::IsNullOrWhiteSpace($script:TwitchData.ApiKey) -and $script:TwitchData.GetTotalCount() -eq 0) {
            Write-Warning "No cached data. Exported file will not contain any entries."
        }

        if ($Force -and -not $PSBoundParameters.ContainsKey("Confirm")) {
            $ConfirmPreference = "None"
        }

        # DateTime string formatting ("yyyy-MM-ddTHH:mm:ssZ" -> "2020-05-09T05:35:45Z")
        $DateTimeFormatting = "yyyy-MM-ddTHH:mm:ssZ"

        # Persistence parameter overrides
        if ($_PersistConfig) {
            if ($script:PersistFormatting.HasFlag([SXRPersistFormat]::Compress)) {
                $Compress = $true
            }
            if ($script:PersistFormatting.HasFlag([SXRPersistFormat]::NoMapping)) {
                $ExcludeClipMapping = $true
            }
        }

        # ==== METADATA ====
        $Metadata = [pscustomobject]@{
            schema = $script:SchemaVersion
        }
        if ($_PersistConfig) {
            $Metadata | Add-Member -NotePropertyName "_persist" -NotePropertyValue $script:PersistFormatting
        }

        # Handle API key
        if ($ExcludeApiKey) {
            $ExportApiKey = ""
        }
        else {
            $ExportApiKey = $script:TwitchData.ApiKey
        }

        $ConvertedUserInfoCache = [List[pscustomobject]]::new()
        $ConvertedClipInfoCache = [List[pscustomobject]]::new()
        $ConvertedVideoInfoCache = [List[pscustomobject]]::new()

        # Convert UserInfoCache to List
        $script:TwitchData.UserInfoCache.GetEnumerator() | ForEach-Object {
            $ConvertedUserInfoCache.Add(
                [pscustomobject]@{
                    name = $_.Key
                    id   = $_.Value
                }
            )
        }

        # Convert ClipInfoCache to List
        $script:TwitchData.ClipInfoCache.GetEnumerator() | ForEach-Object {
            $ClipMappingList = [List[pscustomobject]]::new()

            if (-not $ExcludeClipMapping) {
                # Convert clip mapping data to List
                $_.Value.Mapping.GetEnumerator() | ForEach-Object {
                    $ClipMappingList.Add(
                        [pscustomobject]@{
                            user   = $_.Key
                            result = $_.Value
                        }
                    )
                }
            }

            $ConvertedClipInfoCache.Add(
                [pscustomobject]@{
                    slug    = $_.Key
                    offset  = $_.Value.Offset
                    video   = $_.Value.VideoID
                    created = $_.Value.Created.ToString($DateTimeFormatting)
                    mapping = $ClipMappingList
                }
            )
        }

        # Convert VideoInfoCache to List
        $script:TwitchData.VideoInfoCache.GetEnumerator() | ForEach-Object {
            $ConvertedVideoInfoCache.Add(
                [pscustomobject]@{
                    video     = $_.Key
                    timestamp = $_.Value.ToString($DateTimeFormatting)
                }
            )
        }

        # Bundle data together for converting to JSON
        $StagedTwitchData = [pscustomobject]@{
            config         = $Metadata
            ApiKey         = $ExportApiKey
            UserInfoCache  = $ConvertedUserInfoCache
            ClipInfoCache  = $ConvertedClipInfoCache
            VideoInfoCache = $ConvertedVideoInfoCache
        }
    }

    Process {
        # Save Json string ("-Depth 4" required in order to include clip/username mapping)
        $DataAsJson = $StagedTwitchData | ConvertTo-Json -Compress:$Compress -Depth 4

        # Check if path exists
        if (Test-Path $Path) {
            if ($PSCmdlet.ShouldProcess($Path, "Write File")) {
                $DataAsJson | Out-File $Path -Force:$Force -NoClobber:$NoClobber -Confirm:$false
            }
        }
        else {
            # Path doesn't exist

            if ($PSCmdlet.ShouldProcess($Path, "Create File")) {
                # Create placeholder file, including any missing directories
                # Override ErrorAction preferences because Out-File does not create missing directories and will fail anyway
                [void] (New-Item $Path -ItemType File -Force:$Force -Confirm:$false -ErrorAction Stop)
                $DataAsJson | Out-File $Path -Confirm:$false
            }
        }
    }
}