3

I'm using the class keyword in a powershell script.

I want to serialize instances of my custom class, but I observe that $null members are serialized as "" instead of null.

Reproduction:

Class foo{
    [string]$X
}

[foo]@{ x = $null } | ConvertTo-Json

$foo = New-Object foo
$foo.X = $null

$foo | ConvertTo-Json

Outputs :

{
    "X":  ""
}
{
    "X":  ""
}

But I'm expecting :

{
    "X":  null
}
{
    "X":  null
}

As a side note, this works :

@{ X = $null } | ConvertTo-Json

It outputs expected :

{
    "X":  null
}

Is there a way to fix this ?

PS: if it matters $PSVersionTable outputs:

Name                           Value                                                                                                                                                                     
----                           -----                                                                                                                                                                     
PSVersion                      5.1.19041.546                                                                                                                                                             
PSEdition                      Desktop                                                                                                                                                                   
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                                                                                                   
BuildVersion                   10.0.19041.546                                                                                                                                                            
CLRVersion                     4.0.30319.42000                                                                                                                                                           
WSManStackVersion              3.0                                                                                                                                                                       
PSRemotingProtocolVersion      2.3                                                                                                                                                                       
SerializationVersion           1.1.0.1                                                                                                                                                                   
Steve B
  • 36,818
  • 21
  • 101
  • 174
  • 3
    No, there's no way to fix that due to the way PowerShell implements typing - assignment to a variable that has been type-cast `[string]` will never result in `$null`, always an empty string – Mathias R. Jessen Oct 21 '20 at 13:55
  • If you are able to use 3rd party libraries for this project you could also check out NewtonSoft. I think it's kind of the industry standard in .NET for dealing with json and you get a lot more control of the output – Efie Oct 21 '20 at 15:21

2 Answers2

4

Is there a way to fix this ?

No

(a workaround exists depending on the level of control you have over the input, but there's no way to assign $null directly to a [string]-typed PowerShell class property)

Runtime typing enforcement in PowerShell classes doesn't rely on .NET's implicit type conversion rules - this is offloaded to PowerShell's existing language infrastructure and the conversion logic found there instead, and the default type converter for [string] always returns an instance, never null.

You can observe the same behavior when typing variables:

Remove-Variable X -Force

# Let's assign $null to a variable with no type constraints
$X = $null
# True - we can assign $null to a variable without type constraints just fine
$null -eq $X

# Let's apply a type constraint and assign `$null` again
[string]$X = $null
# False - actual value assigned after type constraint applied is not $null anymore
$null -eq $X
# True - PowerShell has converted $null to ''
'' -eq $X

So you have to type $X as something other than a string ([object] for example) if you want it to retain $null values

Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
3

Mathias R. Jessen's helpful answer explains the problem well: if you assign $null to a [string] typed property or variable, you'll end up with '' (the empty string).

There is an - obscure - workaround, which assumes that you have control over the values you assign:

You can assign [NullString]::Value to a [string]-type-constrained variable or property to make it store a $null value:

Class foo {
  # Default to $null
  [string] $X = [NullString]::Value
}

[foo]::new() | ConvertTo-Json

[foo] @{ X = [NullString]::Value } | ConvertTo-Json

($o = New-Object foo).X = [NullString]::Value
$o | ConvertTo-Json

Note:

  • The primary purpose of [NullString]::Value is to allow passing true null ($null) values to string-typed parameters of .NET Methods, which wouldn't work when passing [string]-typed PowerShell variables or properties, which on assignment invariably convert $null to '' (the empty string), as explained in Mathias' answer.

  • While you can use it in pure PowerShell code as well, as shown above, be mindful that other code that accepts [string]-typed values may not expect them to ever be $null, and that passing a value to another [string]-constrained (parameter) variable again converts to ''.

  • See this answer for more information.

The above yields:

{
  "X": null
}
{
  "X": null
}
{
  "X": null
}
mklement0
  • 382,024
  • 64
  • 607
  • 775