7

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

2
  • 1
    That's an interesting set of observations, but I'm not sure what your question is. What are you trying to achieve? Commented Jun 2, 2012 at 11:34
  • I'm pretty sure that my answer actually resolved your issue without requiring a change in library/etc... But I saw no comments on my answer, nor a bounty assignment to my answer. Commented Jun 14, 2012 at 15:08

2 Answers 2

9
+250

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
Sign up to request clarification or add additional context in comments.

3 Comments

notice the case where I assign a variable whose value is 1 and that fails, but if I convert it to a string and then convert it back to a int it works fine. No hashes involved here
@pm100 Yes, I saw that. You're still assigning the manually converted value to $o.B and $o is indeed a hash. My solution will allow you to do that without requiring the caller to perform a manual check on any values you need to convert, or building up the source object in any particular way.
@pm100 I just added support for objects and PSObjects so you don't even have to use hashes anymore if you don't want to.
3

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}

1 Comment

i actually wanted an explanation of what was going on, I already have a work round, but have the points anyway

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.