Powershell: Test-Json doesn't recognize JSON arrays or primitives

Created on 17 Dec 2019  Â·  17Comments  Â·  Source: PowerShell/PowerShell

Steps to reproduce

'42' | Test-Json | Should -BeTrue
'[ 1, 2 ]' | Test-Json | Should -BeTrue

Expected behavior

The tests should succeed, given that these are valid JSON strings and that piping them to ConvertFrom-Json works just fine:

# These tests pass.
'42' | ConvertFrom-Json | Should -Be 42
'[ 1, 2 ]' | ConvertFrom-Json | Should -Be 1, 2

Actual behavior

Both tests fail.

Environment data

PowerShell Core 7.0.0-rc.1
Area-Cmdlets-Utility Issue-Question

Most helpful comment

Hello,

what does that mean now? The world will be stuck with a broken command? Could you please make a clear statement, whether this issue will be abandoned or resolved?

Kind regards

Konstantin

All 17 comments

@mklement0 Could you please add more info why it should work in the way? Maybe reference to a standard or a quote from the standard. It would help to avoid regressions in follow fix.

@iSazonov, I've added a link to json.org to the OP, and also a demonstration that ConvertFrom-Json itself happily accepts such inputs.

We use NewtonSoft JObject.Parse() which throws. I tried System.Text.Json.JsonDocument.Parse() and … most of our tests failed because examples in the issue and out tests in Test-Json.Tests.ps1 use single quotes but Json standard allows only double quotes.
So we should decide what to do because switching to JsonDocument.Parse() looks like a breaking change.
With double quotes the tests and the issue examples are passed if JsonDocument.Parse() is used.

use single quotes but Json standard allows only double quotes.

The single quotes aren't in the _data_, they are only there as _PowerShell's string delimiter_.

What Test-Json sees is just 42 and [ 1, 2 ] and both are valid JSON strings.

I haven't looked at the internals, but it's obvious that whatever ConvertFrom-Json manages to parse should be considered valid by Test-Json.

+1 Encountered the same problem here - would love to see this resolved!

@jochenvanwylick You could download artifact from #11397, check your scenarios and report issues there.

@iSazonov: In the current implementation,

https://github.com/PowerShell/PowerShell/blob/7b33cfe8470e100e4de3942eecbb57549e293747/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs#L70

should be:

```c#
parsedJson = JToken.Parse(Json);


Only `JToken` correctly parses JSON documents such as `42`  and `[ 1, 2 ]`, whereas `JObject` accepts just a _single object_ (dictionary).

Or, better yet, use the same technique that `ConvertFrom-Json` uses:

https://github.com/PowerShell/PowerShell/blob/7b33cfe8470e100e4de3942eecbb57549e293747/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs#L179-L187

Note that the code there contains a workaround for a NewtonSoft bug that's no longer needed.

--- 

In a `System.Text.Json`-based implementation, `System.Text.Json.JsonDocument.Parse()` is indeed the right tool.

---

All these tests succeed:

```powershell
# Valid JSON
'42', '[ 1, 2 ]', '{ "foo": 1 }' | % {
    {  [System.Text.Json.JsonDocument]::Parse($_) } | Should -Not -Throw
    {  [Newtonsoft.Json.Linq.JToken]::Parse($_)  } | Should -Not -Throw
}

# Invalid JSON
'a', '[', '{ "foo": 1 }, { "bar": 2 }' | % {
    {  [System.Text.Json.JsonDocument]::Parse($_) } | Should -Throw
    {  [Newtonsoft.Json.Linq.JToken]::Parse($_)  } | Should -Throw
}

@mklement0 Thanks! I think we should move to Core API if possible. Sooner or later it will happen anyway. If there are no serious problems, it is better to do it "sooner".

Has there been any progress on this?

A Redditor recently made a similar comment. I responded with a link to this issue.

Basically, I said that they could use ConvertFrom-Json | ConvertTo-Json | Test-Json to perform tests.

'[ { name : "Joe" } ]' | ConvertFrom-Json | ConvertTo-Json | Test-Json

Is this the best advice for PowerShell 7 GA?

@thedavecarroll, yes, it's unfortunate that this wasn't addressed in time for 7.0

PR #11397 will implicitly fix these issues, if I understand correctly, but due to its move to a different underlying API it will bring breaking changes, notably no longer being able to use _single_-quoted JSON (right, @iSazonov?).

I wasn't even aware of the other issue that Test-Json doesn't support its input as an _array_ of lines, the way ConvertFrom-Json does (e.g., '{ "foo": ', '"bar" }' | Test-Json fails, which succeeds with ConvertFrom-Json)

As for a workaround: It's sufficient to use only ConvertFrom-Json, and derive from its failure whether the input is valid JSON or not:

$valid = try { $null = '[ { name : "Joe" } ]' | ConvertFrom-Json; $true } catch { $false }

@mklement0 Yes.
Currently we are waiting .Net 5.0 public preview to continue.

Might we get another update on this issue?

@iSazonov said -

Currently we are waiting .Net 5.0 public preview to continue.

Looks like this has now happened.

With regards to suggested workarounds using ConvertFrom-Json - unfortunately that's no good if you are trying to test against a schema.

We have PRs for ConvertFrom-Json and Test-Json a long time without review. Unfortunately, we missed this time. I think it makes no sense to continue this now. .Net 5 will be frozen soon for release and we have no chance to fix something there if we find problems.

Hello,

what does that mean now? The world will be stuck with a broken command? Could you please make a clear statement, whether this issue will be abandoned or resolved?

Kind regards

Konstantin

I'm encountering the same issue.
Please fix it before .net 5.0

In case it's useful to anyone, I've had some success working around this issue with code like the following:

try
{
    # Work around a limitation of Test-Json, which can't parse top-level arrays:
    # Wrap the actual json as the value of a single property "_" in an object.
    $schemaPrefix = "{`"type`":`"object`",`"properties`":{`"_`":"
    $schemaBody = $schema -replace "`"\`$ref`"\s*:\s*`"#/", "`"`$ref`":`"#/properties/_/"
    $schemaSuffix = "}}"
    $wrappedSchema = $schemaPrefix + $schemaBody + $schemaSuffix
    $wrappedJson = "{`"_`":" + $json +"}"
    Test-Json -Json $wrappedJson -Schema $wrappedSchema -ErrorAction Stop
    return $True
}
catch
{
    # Have to unwrap the path in any schema error
    $schemaError = $_.ErrorDetails.Message.Replace("#/_.", "#/").Replace("#/_[", "#/[")
    Write-Error "$($_.Exception.Message) $($schemaError)"
    return $False
}

You could try artifact from #11397 (now it is on 5.0 RC2) - both tests from the OP are passed.

single-quoted JSON

This was rejected by .Net team because of (1) low audience, (2) performance lack. https://github.com/dotnet/runtime/issues/31608 It is true for Test-Json too. I believe we have no need to think about this today.

Was this page helpful?
0 / 5 - 0 ratings