skip to Main Content

I’m looking for a way to check if file provided to the script matches a schema that is required by the script. My powershell version is 5 so no access to Test-Json cmndlet. Any ideas?

JSON schema to be checked against.

$json_content = @"
{ 
    "buttons":{
        "1":"Zenek",
        "2":"NetExt"
        },
    "Text":{
        "1":"",
        "2":""
        }
}
"@

Example file to be checked (positively)

$json_content = @"
{ 
    "buttons":{
        "1":"Zenek",
        "2":"NetExt"
        },
    "Text":{
        "1":"THIS IS OK",
        "2":"CORRECT"
        }
}
"@

Example file 2 to be checked (and failed)

$json_content = @"
{ 
    "I MADE CHANGES HERE":{
        "1":"Zenek",
        "2":"NetExt"
        },
    "PLEAE FAIL ME":{
        "1":"",
        "2":""
        }
}
"@

2

Answers


  1. PowerShell 7 comes with Test-Json, a command with which you can validate a JSON document against a JSON schema.

    First, we’ll need to define a schema that describes the required JSON format:

    $schema = @'
    {
      "type": "object",
      "properties": {
        "buttons": {
          "$ref": "#/$defs/stringtuple"
        },
        "Text": {
          "$ref": "#/$defs/stringtuple"
        }
      },
      "$defs": {
        "stringtuple": {
          "type": "object",
          "properties": {
            "1": { "type": "string" },
            "2": { "type": "string" }
          }
        }
      },
      "required": ["buttons","Text"]
    }
    '@
    

    Here, we describe a root object with two properties, buttons and Text, both of which are required to have a value described by the nested stringtuple schema – an object that consists of two string properties 1 and 2.

    Now it’s simply a question of calling Test-Json:

    $isValidJson = Test-Json $json_content -Schema $schema
    
    Login or Signup to reply.
  2. This approach will compare the noteproperty members between 2 PSObjects and will return false if any of the following occurs:

    • The template contains properties that are not found in the target.
    • The target contains properties that are not found in the template.
    • A property which exists in both has different data types.

    The basic idea is:

    1. Convert the JSON into psCustomObjects.
    2. Compare the noteproperties of the highest level object of the template and target.
    3. Recursively compare any properties which are themselves psCustomObjects (but return false as soon as a mismatch is found)

    Please note that this will only work with noteproperties. If you try to compare complex objects containing methods or other property types then this will not work.

    Test Data:

    #this represents the structure that we expect. This is our template
    $templateJson = @"
    { 
        "buttons":{
            "1":"Zenek",
            "2":"NetExt"
            },
        "Text":{
            "1":"",
            "2":""
            }
    }
    "@
    $expectedJsonStructure = $templateJson | ConvertFrom-Json
    
    #this is a test case that should be considered valid
    $expectPassJSON = @"
    { 
        "buttons":{
            "1":"dummyValue",
            "2":"dummyValue"
            },
        "Text":{
            "1":"dummyValue",
            "2":"dummyValue"
            }
    }
    "@
    $expectPassStructure = $expectPassJSON | ConvertFrom-Json
    
    #this test case should fail because "buttons" is now a [string] instead of a [pscustomobject]
    $expectFailJSON = @"
    { 
        "buttons": "Fail on type mismatch",
        "Text":{
            "1":"dummyValue",
            "2":"dummyValue"
            }
    }
    "@
    $expectFailStructure = $expectFailJSON | ConvertFrom-Json
    
    #this test case should fail because "text.1" is now an integer instead of a string
    $expectFailJSON2 = @"
    { 
        "buttons":{
            "1":"dummyValue",
            "2":"dummyValue"
            },
        "Text":{
            "1":0,
            "2":"dummyValue"
            }
    }
    "@
    $expectFailStructure2 = $expectFailJSON2 | ConvertFrom-Json
    
    #this test case should fail because the property names do not match the template
    $expectFailJSON3 = @"
    { 
        "buttonsDifferentName":{
            "1":"dummyValue",
            "2":"dummyValue"
            },
        "TextDifferentName":{
            "1":"dummyValue",
            "2":"dummyValue"
            }
    }
    "@
    $expectFailStructure3 = $expectFailJSON3 | ConvertFrom-Json
    

    Function for comparing the object structures:

    Function Verify-PSObjectStructure{
        [CmdletBinding()]
        param(
            [parameter(Mandatory=$true)][psobject]$template,
            [parameter(Mandatory=$true)][psobject]$target
        )
    
        #returns false after finding any mismatches
    
        $anyFail = $false
        $templateProps = $template | Get-Member -MemberType NoteProperty
        $targetProps = $target | Get-Member -MemberType NoteProperty
    
        #check for properties in the template that are not found in the target
        if(@($templateProps.Name).Where({$_ -notin $targetProps.Name}).Count -gt 0){
            return $false
        }
    
        #check for properties in the target that are not found in the template
        if(@($targetProps.Name).Where({$_ -notin $templateProps.Name}).count -gt 0){
            return $false
        }
    
        #for all properties shared between the objects, check if their values have the same type
        $sharedProps = @($templateProps.Name).Where({$_ -in $targetProps.Name})
        $sharedProps.ForEach({
            if($template.($_).GetType() -ne $target.($_).GetType()){
                $anyFail = $true
            }
        })
    
        if($anyFail){
            return $false
        }
    
        #for any properties that are PSObjects, do a recursive call to compare their properties
        $sharedProps.ForEach({
            if($template.($_).GetType() -eq [System.Management.Automation.PSCustomObject]){
                if( -not(Verify-PSObjectStructure -template $template.$_ -target $target.$_)){
                    $anyFail = $true
                } 
            }
        })
    
        return -not $anyFail
    }
    
    #should return True
    Verify-PSObjectStructure -template $expectedJsonStructure -target $expectPassStructure
    #should return False
    Verify-PSObjectStructure -template $expectedJsonStructure -target $expectFailStructure
    

    Function for seeing what the custom Verify-PSObjectStructure function is doing:

    Function Explain-PSObjectStructure(){
        [CmdletBinding()]
        param(
            [parameter(Mandatory=$true)][psobject]$template,
            [parameter(Mandatory=$true)][psobject]$target,
            [parameter(Mandatory=$true)][psobject]$objectDescription
        )
        $templateProps = $template | Get-Member -MemberType NoteProperty
        $targetProps = $target | Get-Member -MemberType NoteProperty
    
        Write-Host ([string]::Concat('*** Explaining properties of "', $objectDescription, '" ***'))
    
        $templatePropertiesNotInTarget = @($templateProps.Name).Where({$_ -notin $targetProps.Name})
    
        if($templatePropertiesNotInTarget.Count -gt 0){
            Write-Host 'FAIL: Template properties not found in target properties'
            $templatePropertiesNotInTarget | Out-Host
        }
        else{
            Write-Host 'PASS: All properties in the template are found in the target. Shared properties: '
            $templateProps.Name | Out-Host
        }
    
        $targetPropertiesNotInTemplate = @($targetProps.Name).Where({$_ -notin $templateProps.Name})
        if($targetPropertiesNotInTemplate.count -gt 0){
            Write-Host 'FAIL: Target properties not found in template properties'
            $targetPropertiesNotInTemplate | Out-Host
        }
        else{
            Write-Host 'PASS: All properties in the target are found in the template.'
        }
    
        $sharedProps = @($templateProps.Name).Where({$_ -in $targetProps.Name})
        $propertiesWithDifferentTypes = [System.Collections.ArrayList]::new()
        $sharedProps.ForEach({
            if($template.($_).GetType() -ne $target.($_).GetType()){
                [string]::Concat('FAIL: Target property "', $_, '" of type "', $target.($_).GetType(), '" does not match the template. Expected type "', $template.($_).GetType(), '"') | Out-Host
                $propertiesWithDifferentTypes.Add($_) | Out-Null
            }
            else{
                [string]::Concat('PASS: Property "', $_, '" type matches between template and target') | Out-Host
            }
        })
    
        $propertiesThatAreObjects = $sharedProps.ForEach({
            if($template.($_).GetType() -eq [System.Management.Automation.PSCustomObject]){
                $_
            }
        })
    
        if($propertiesThatAreObjects.Count -gt 0){
            [string]::Concat('Checking properties of all psobjects that belong to "', $objectDescription, '"') | Out-Host
            $propertiesThatAreObjects.Name | Out-Host
        }
        else{
            [string]::Concat('Object "', $objectDescription, '" does not contain any shared properties which are psobjects') | Out-Host
        }
    
        $propertiesThatAreObjects.ForEach({
            if($_ -in $propertiesWithDifferentTypes){
                [string]::Concat('Property "', $_, '" type does not match between template and target. Skipping detailed comparison') | Out-Host
            }
            else{
                Explain-PSObjectStructure -template $template.$_ -target $target.$_ -objectDescription $_
            }
        })
    
        [string]::Concat('*** finished processing all properties and child items of "', $objectDescription,'" ***') | Out-Host
    }
    
    #all items checked should yield a PASS message
    Explain-PSObjectStructure -template $expectedJsonStructure -target $expectPassStructure -objectDescription 'ParentObject'
    #at least one item checked should yield a FAIL message
    Explain-PSObjectStructure -template $expectedJsonStructure -target $expectFailStructure -objectDescription 'ParentObject'
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search