1

Let’s say I have a PSObject with an integer and a date that I want to serialize to JSON and have recipients be able to deserialize it. In JavaScript to do this I get the following JSON:

JSON.stringify({Date: new Date(), Number: 23})
"{"Date":"2014-09-10T14:11:27.092Z","Number":23}"

JSON.parse(JSON.stringify({Date: new Date(), Number: 23}))
>> Object {Date: "2014-09-10T14:13:28.950Z", Number: 23}

That looks like pretty standard JSON that can be interpreted correctly by JavaScript.

However, it appears that PowerShell's ConvertTo-Json is so inconsistent with DateTime serialization that it can't even convert itself back and forth correctly. To fully illustrate this:

PS C:\dev> $testObj = New-Object -TypeName PSobject -Property @{ Date = Get-Date; Number = 23; }
PS C:\dev> $testObj

Number Date
------ ----
    23 9/10/2014 9:52:14 AM

PS C:\dev\git\strawman> $testObj | ConvertTo-Json
{
    "Number":  23,
    "Date":  {
                 "value":  "\/Date(1410357134361)\/",
                 "DisplayHint":  2,
                 "DateTime":  "Wednesday, September 10, 2014 9:52:14 AM"
             }
}

PS C:\dev> $testObj | ConvertTo-Json | ConvertFrom-Json

Number Date
------ ----
    23 @{value=9/10/2014 1:52:14 PM; DisplayHint=2; DateTime=Wednesday, September 10, 2014 9:52:14 AM}

Not only is the JSON it produces for the date property not valid at all and can't be interpreted correctly by JavaScript, but PowerShell's ConvertFrom-Json can't even interpret it correctly to give it the same looking object back.

Is there any way to correctly serialize a PSObject with a DateTime to valid JSON content that can be correctly deserialized by recipients?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
KallDrexx
  • 27,229
  • 33
  • 143
  • 254
  • You probably want to look here: http://stackoverflow.com/questions/206384/how-to-format-a-microsoft-json-date – Alexander Obersht Sep 10 '14 at 14:47
  • That doesn't solve the serialization issue, that only attempts to answer a deserialization issue that isn't even related to this – KallDrexx Sep 10 '14 at 14:52
  • You don't need a custom object to see this. `Get-Date | ConvertTo-Json | ConvertFrom-Json` gives you a custom object with three properties: value, DisplayHint, and DateTime. This example is actually in the docs for ConvertFrom-Json so I'm guessing the behavior is by design. Frankly, this behavior is bizarre, but I don't think .NET has never handled DateTime serialization into JSON particularly well. Part of the problem is that there is no type in JSON for a DateTime, so anything you do is simply convention. The current convention is to use an ISO 8601 format, but that is not spec'd. – Mike Zboray Sep 10 '14 at 16:53
  • Actually there was a reason I used a custom object. You can get a straight `Get-Date` to work via `Get-Date | Select -Property Date | ConvertTo-Json`, but this does not seem to help with complex object serialization. – KallDrexx Sep 10 '14 at 17:13
  • I don't see how that works. That is only going to save the date component. The time component is lost. Basically you need a way for `Get-Date` to work without having to explicitly decide which properties to serialize. Also, the DisplayHint property is something only added by Get-Date. Is that your normal case, values from Get-Date or was it just an example? – Mike Zboray Sep 11 '14 at 04:05
  • You are correct, so yeah that option is out the window. – KallDrexx Sep 11 '14 at 12:28

1 Answers1

3

I ended up quickly writing up a function to convert any PSObject to a PSObject that can be converted to cleaner JSON:

<#
.SYNOPSIS
    Creates a new PSObject where all properties of the original object that are not able to be
    properly serialized to JSON are converted to a value which can be properly converted to JSON.

    This includes the following types:
    *   DateTime

    This conducts a deep property search
.Example 
    Convert an custom PSObject to have parsable dates in Json

    $customObject = New-Object -TypeName PSobject -Property @{ Date = Get-Date; Number = 23; InnerDate = New-Object -TypeName PSObject -Property @{Date=Get-Date;} }

    ## BAD Json
    PS C:\dev> $customObject | ConvertTo-Json
    {
        "Date":  {
                     "value":  "\/Date(1410372629047)\/",
                     "DisplayHint":  2,
                     "DateTime":  "Wednesday, September 10, 2014 2:10:29 PM"
                 },
        "Number":  23,
        "InnerDate":  {
                          "Date":  {
                                       "value":  "\/Date(1410372629047)\/",
                                       "DisplayHint":  2,
                                       "DateTime":  "Wednesday, September 10, 2014 2:10:29 PM"
                                   }
                      }
    }

    ## Good Json
    PS C:\dev> $customObject | ConvertTo-JsonifiablePSObject | ConvertTo-Json
    {
        "Date":  "2014-09-10T14:10:29.0477785-04:00",
        "Number":  23,
        "InnerDate":  {
                          "Date":  "2014-09-10T14:10:29.0477785-04:00"
                      }
    }

#>
function ConvertTo-JsonifiablePSObject
{
    param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [PSObject]$Object
    )

    $newObjectProperties = @{}

    foreach ($property in $Object.psobject.properties)
    {
        $value = $property.Value

        if ($property.TypeNameOfValue -eq "System.Management.Automation.PSCustomObject")
        {
            $value = ConvertTo-JsonifiablePSObject -Object $property.Value
        }
        elseif ($property.TypeNameOfValue -eq "System.DateTime")
        {
            $value = $property.Value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK")
        }

        $newObjectProperties[$property.Name] = $value
    }

    return New-Object -TypeName PSObject -Property $newObjectProperties
}
KallDrexx
  • 27,229
  • 33
  • 143
  • 254