8

I´m writing a script in powershell 2.0 and an upgrade to 3.0 or higher is not possible right now. In this script I try to serialize some data to JSON with the code from this link (PowerShell 2.0 ConvertFrom-Json and ConvertTo-Json implementation):

function ConvertTo-Json20([object] $item){
    add-type -assembly system.web.extensions
    $ps_js=new-object system.web.script.serialization.javascriptSerializer
    return $ps_js.Serialize($item)
}

My problem is that I somehow get a circular reference and I really don´t know why. I set up a litte piece of test data and the structure looks in powershell like this:

$testRoot = @{
    "id" = "1"
    "children" = @( 
        @{
            "id" = "2"
            "children" = @( 
                @{
                    "id" = "2";
                };
                @{
                    "id" = "3";
                }
            );
        };
        @{
            "id" = "4"
            "children" = @( 
                @{
                    "id" = "5";
                }
            );
        }
    )
}

I know it looks junky, but I just need it in this format.

The structures I need to serialize have a few more layers, so even more "children" and there is the point where it gets strange.

When I try this:

ConvertTo-Json20 $testRoot

everything works fine. The structure gets parsed like this:

{
   "id":"1",
   "children":[
        {
            "id":"2",
            "children":[
               {
                   "id":"2"
               },
               {
                   "id":"3"
               }
            ]
        },
        {
            "id":"4",
            "children":[
               {
                   "id":"5"
               }
            ]
        }
   ]
}

But now comes the problem. As mentioned the structure has more layers, so I try this which just sets the data in an array.

ConvertTo-Json20 @($testRoot)

But it does not work I just get an error message saying:

Exception in method "Serialize" with 1 argument(s):  
"While serializing an object of type "System.Management.Automation.PSParameterizedProperty" a circular reference was discovered."
At C:\Users\a38732\Desktop\Temp.ps1:34 symbol:28
+     return $ps_js.Serialize <<<< ($item)
+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

(I translated the error message from german, so there might be some words different in the english version...)

kuujinbo
  • 9,272
  • 3
  • 44
  • 57
  • I just tried it with Powershell 4.0 on another machine and it worked with the build in json serializer. ConvertTo-Json @($testRoot) – Michael Zappe Aug 26 '16 at 09:17
  • Does somebody know how to tweek the javascriptSerializer to serialize that? The documentation does not help very much... – Michael Zappe Aug 26 '16 at 09:25
  • Your example is malformed. Entries in arrays are separated by commas, not semicolons. The function also doesn't take an array, it takes a single `[object]`. – Maximilian Burszley Jul 16 '18 at 20:10
  • 1
    @TheIncorrigible1 - agree that array elements are **normally** separated by commas, but actually the `@( )` [array subexpression operator is special](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-6#special-operators). To quote: "_Returns the result of one or more statements as an array_." e.g. `@(Get-Date; Start-Sleep -s 1; Get-Date)`. And since an `array` is a `object`, the function is perfectly valid. One problem is that `JavaScriptSerializer.Serialize()` is useless for anything but **simple** JSON. – kuujinbo Jul 17 '18 at 22:51
  • If it is just about *serializing* a (complex) object for reuse by the same system or exchanging with another *PowerShell* system, you might consider the [`ConvertTo-Expression` cmdlet from the PowerShell Gallery](https://www.powershellgallery.com/items?q=convertto-expression&x=0&y=0) which is downwards compatible with PSv2.0. The results can easily be invoked (deserialized) with `Invoke-Expression` or just an ampersand (`&`) or *dot sourcing* a `.ps1` file containing the results. – iRon Jul 18 '18 at 19:30

1 Answers1

4

One problem is the use of the JavaScriptSerializer class itself. As of this date the documentation itself concedes it should not be used to serialize nor deserialize JSON. To quote:

Json.NET should be used serialization and deserialization.

If you're able to use third-party libraries like Json.NET, here's a simple function that does what you need given the data structure in the OP:

function ConvertTo-JsonNet {
   [CmdletBinding()]
    param(
        [Parameter(Mandatory)] $object,
        [Parameter(Mandatory)] [string]$jsonNetPath,
        [switch]$indent,
        [switch]$preserveReferencesHandling
    )
    Add-Type -Path $jsonNetPath;

    $formatting = if ($indent.IsPresent) { [Newtonsoft.Json.Formatting]::Indented; }
    else { [Newtonsoft.Json.Formatting]::None; }

    $settings = New-Object Newtonsoft.Json.JsonSerializerSettings;
    if ($preserveReferencesHandling.IsPresent) { 
        $settings.PreserveReferencesHandling = [Newtonsoft.Json.PreserveReferencesHandling]::Objects;
    }

    [Newtonsoft.Json.JsonConvert]::SerializeObject($object, $formatting, $settings);
}

Simple usage, assuming Newtonsoft.Json.dll is in the same directory as your script:

$dllPath = Join-Path $PSScriptRoot 'Newtonsoft.Json.dll';
ConvertTo-JsonNet @($testRoot) $dllPath;

Output:

[{"id":"1","children":[{"id":"2","children":[{"id":"2"},{"id":"3"}]},{"id":"4","children":[{"id":"5"}]}]}]

You can manually download the .dll from the nuget package project site. It has a .nupkg file extension, but it's a zipped archive, so rename the extension to .zip and you're set. In the lib sub-directory there are .dll files for .NET versions from 2.0 through 4.5.

kuujinbo
  • 9,272
  • 3
  • 44
  • 57