Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

oddity using newtonsoft json.net with powershell

I have

function Foo($a, $b)
{
    $o = @{}
    $o.A = $a
    $o.B = $b
    $post = @{}
    $post.X="x"
    $post.entity =$o
    $newton::SerializeObject($post)
}

then do

foo "a" "b"

I get

Exception calling "SerializeObject" with "1" argument(s): "Self referencing loop detected for property 'Value' with type 'System.Management.Automation.PSParameterizedProperty'. Path 'entity.Members[0]'."

however

function Foo2($o)
{
    $post = @{}
    $post.X="x"
    $post.entity =$o
    $newton::SerializeObject($post)
}

foo2 @{a="a"; b="b"}

works fine. Also

function foo3($a, $b)
{
   $o = @{}
   $o.A = $a
   $o.B = $b
   $newton::SerializeObject($o)
}

foo3 "a" "b"

works but

foo3 "a" 1

fails

The latter can be made to work by doing

 $o.B= [Int32]::Parse($b.Tostring())

Which all seems very odd

powershell v2 on windows 7, json.net 4.4.5

like image 432
pm100 Avatar asked May 24 '12 20:05

pm100


2 Answers

The JavaScriptSerializer from the .NET framework also has a similar problem with serializing PowerShell's hashes. I suspect it something slightly odd in the PowerShell type system. You could skip Json.Net altogether and roll your own.

Below is something to start you off. It's likely not as robust as PowerShell 3's built-in ConvertTo-Json cmdlet, but I think it's mostly complete.

Here are all of your examples, in working order.

# See below for ConvertTo-Json.psm1
Import-Module ConvertTo-Json

function Foo($a, $b)
{
    $o = @{}
    $o.A = $a
    $o.B = $b
    $post = @{}
    $post.X="x"
    $post.entity =$o
    ConvertTo-Json $post
}

function Foo2($o)
{
    $post = @{}
    $post.X="x"
    $post.entity =$o
    ConvertTo-Json $post
}

function foo3($a, $b)
{
   $o = @{}
   $o.A = $a
   $o.B = $b
   ConvertTo-Json $o
}

PS> foo "a" "b"
{"entity":{"A":"a","B":"b"},"X":"x"}

PS> foo2 @{a="a"; b="b"}
{"entity":{"a":"a","b":"b"},"X":"x"}

PS> foo3 "a" "b"
{"A":"a","B":"b"}

PS> foo3 "a" 1
{"A":"a","B":1}

And here's the PowerShell module that implements ConvertTo-Json.

# Save these contents to Modules\ConvertTo-Json\ConvertTo-Json.psm1 in your
# PowerShell documents folder, and load them in your $profile using the
# "Import-Module ConvertTo-Json" cmdlet. This will make the ConvertTo-Json cmdlet
# available for use.

Set-StrictMode -Version Latest

function convertToJsonNull($InputObject) {
    "null"
}

function convertToJsonArray($InputObject) {
    $value = ($InputObject | %{ convertToJson $_ }) -join ','
    "[$value]"
}

function convertToJsonHash($InputObject) {
    $value = ($InputObject.Keys | %{
        $name = $_ | asJsonString
        $itemValue = convertToJson ($InputObject[$_])
        '"{0}":{1}' -f $name, $itemValue
    }) -join ','
    "{$value}"
}

function convertToJsonObject($InputObject) {
    $value = ($InputObject | get-member -membertype *property | %{
        $name = $_.Name
        $value = convertToJson ($InputObject.($name))
        '"{0}":{1}' -f ($name | asJsonString), $value
    }) -join ','
    "{$value}"
}

function convertToJsonString($InputObject) {
    '"{0}"' -f ($InputObject | asJsonString)
}

function convertToJsonBool($InputObject) {
    $InputObject.ToString().ToLower()
}

function convertToJsonNumeric($InputObject) {
    "$InputObject"
}

function convertToJsonDate($InputObject) {
    $epoch = [datetime]"1970-01-01T00:00:00Z"
    $elapsed = [long]($InputObject - $epoch).TotalMilliseconds
    '"\/Date({0})\/"' -f $elapsed
}

filter isNumeric() {
    $_ -is [byte]  -or $_ -is [int16]  -or $_ -is [int32]  -or $_ -is [int64]  -or
    $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] -or
    $_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
}

filter asJsonString {
    ($_ -replace '\\', '\\') -replace '"', '\"'
}

function convertToJson($InputObject) {
    if     ($InputObject -eq $null)       { convertToJsonNull    $InputObject }
    elseif ($InputObject -is [array])     { convertToJsonArray   $InputObject }
    elseif ($InputObject -is [hashtable]) { convertToJsonHash    $InputObject }
    elseif ($InputObject -is [datetime])  { convertToJsonDate    $InputObject }
    elseif ($InputObject -is [string])    { convertToJsonString  $InputObject }
    elseif ($InputObject -is [char])      { convertToJsonString  $InputObject }
    elseif ($InputObject -is [bool])      { convertToJsonBool    $InputObject }
    elseif ($InputObject | isNumeric)     { convertToJsonNumeric $InputObject }
    else                                  { convertToJsonObject  $InputObject }
}

function ConvertTo-Json {
    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        $InputObject
    )
    convertToJson $InputObject
}

Export-ModuleMember -Function ConvertTo-Json
like image 50
Damian Powell Avatar answered Oct 19 '22 10:10

Damian Powell


The self referencing loop issue sems to be all about.... the order in which you assign things. The below example works:

function Foo($a, $b)
{
    $o = @{}
    $post = @{}

    $post.entity =$o

    $o.A = $a
    $o.B = $b   

    $post.X="x"

    [Newtonsoft.Json.JsonConvert]::SerializeObject($post)
}

Foo "a" "b"

{"entity":{"A":"a","B":"b"},"X":"x"}

If you convert the type before you pass it in then it will keep your foo3 function generic:

function foo3($a, $b)
{
   $o = @{}
   $o.A = $a
   $o.B = $b
   [Newtonsoft.Json.JsonConvert]::SerializeObject($o)
}


$var2 = [Int32]::Parse((1).Tostring())

Foo3 "a" $var2

{"A":"a","B":1}
like image 36
Peter Avatar answered Oct 19 '22 09:10

Peter