5

Please, observe:

C:\> ''|Get-Member |? { $_.MemberType -eq 'ParameterizedProperty' }


   TypeName: System.String

Name  MemberType            Definition
----  ----------            ----------
Chars ParameterizedProperty char Chars(int index) {get;}


C:\>

This is a very weird property. First of all it is added by Powershell, next it contains an infinite recursive property:

C:\> ''.Chars


IsSettable          : False
IsGettable          : True
OverloadDefinitions : {char Chars(int index) {get;}}
TypeNameOfValue     : System.Char
MemberType          : ParameterizedProperty
Value               : char Chars(int index) {get;}
Name                : Chars
IsInstance          : True



C:\> ''.Chars.Value


IsSettable          : False
IsGettable          : True
OverloadDefinitions : {char Chars(int index) {get;}}
TypeNameOfValue     : System.Char
MemberType          : ParameterizedProperty
Value               : char Chars(int index) {get;}
Name                : Chars
IsInstance          : True



C:\> ''.Chars.GetHashCode()
56544304
C:\> ''.Chars.Value.GetHashCode()
34626228
C:\> ''.Chars.Value.Value.GetHashCode()
3756075
C:\> ''.Chars.Value.Value.Value.GetHashCode()
49108342
C:\> ''.Chars.Value.Value.Value.Value.GetHashCode()
62340979
C:\> ''.Chars.Value.Value.Value.Value.Value.GetHashCode()
24678148
C:\>

The hash code is different every time, so it must be dynamically generated.

Why do I care? I am trying to use a Newtonsoft.Json PowerShell module from PSGallery and it chokes on this property, but only when run in Desktop PowerShell (5.1), not the Core (7.0.3). The problem is that I do not have a minimal reproduction, the input object is quite large. The error I get is:

ConvertTo-JsonNewtonsoft : Exception calling "SerializeObject" with "2" argument(s): "Self referencing loop detected for property 'Value' with type 'System.Management.Automation.PSParameterizedProperty'. Path 'environments[4].conditions.name.Chars'."

No such problem exists in PS Core.

Can someone explain to me what is this property, why we need it and how can we get rid of it?

EDIT 1

I guess it is a problem with the Newtonsoft.Json module. Observe:

[DBG]> [pscustomobject]@{ a = 1} | ConvertTo-Json
{
  "a": 1
}
 [DBG]>  [pscustomobject]@{ a = 1} | ConvertTo-JsonNewtonsoft
{
  "CliXml": "<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\">\r\n  <Obj RefId=\"0\">\r\n    <TN RefId=\"0\">\r\n
 <T>System.Management.Automation.PSCustomObject</T>\r\n      <T>System.Object</T>\r\n    </TN>\r\n    <ToString>@{a=1}</ToString>\r\n    <Obj RefId=\"1\">\r\n      <TNRef RefId=\"0\" />\r\n      <MS>\r\n        <I32 N=\"a\">1</I32>\r\n      </MS>\r\n    </Obj>\r\n    <MS>\r\n      <I32 N=\"a\">1</I32>\r\n    </MS>\r\n  </Obj>\r\n</Objs>"
}
 [DBG]>

It is unable to properly interpret powershell objects. Makes it unusable.

mark
  • 59,016
  • 79
  • 296
  • 580
  • As far as what the property is, 'hello'.Chars(0) evaluates to 'h'. That tells me that it's pulling back the character at the position specified (as a character, not a string). – Mike Shepard Oct 03 '20 at 22:29
  • The parameterized property itself _isn't_ added by PowerShell - see https://learn.microsoft.com/en-US/dotnet/api/system.string.chars. It is only PowerShell's way of _reflecting_ that property that introduces the loop. But the question is: why is the serialization trying to reflect on the properties of a _string_ instance, which shouldn't really happen? Is there an invisible `[psobject]` wrapper involved? – mklement0 Oct 03 '20 at 22:30
  • @mklement0 - please see EDIT 1. The library is bonkers. I will ask another question about json and powershell. – mark Oct 03 '20 at 22:36
  • Perhaps this helps: https://stackoverflow.com/a/58169326/45375 – mklement0 Oct 03 '20 at 22:37
  • Looking at your edit, it looks like it's asking the `[psobject]` == `[pscustomobject]` instance to _serialize itself_ (via `System.Runtime.Serialization.ISerializable`) - which is not surprising, given that the dynamic properties of a PS custom objects aren't properties in a .NET sense. What you're seeing is the CLIXML serialization of a PS custom object. – mklement0 Oct 03 '20 at 22:43
  • I figured it out. Could you have a look at https://stackoverflow.com/questions/64189660/how-to-serialize-an-object-in-powershell-to-json-and-get-identical-result-in-ps ? – mark Oct 03 '20 at 22:45

1 Answers1

0

tl;dr

Your real problem is that neither the Newtonsoft.Json library nor the PowerShell wrapper module for it support [pscustomobject] instances:

The library asks [pscustomobject] instances to serialize themselves, based on the [pscustomobject] ([psobject]) implementing the ISerializable interface.

  • In Windows PowerShell this happens to fail outright, presumably due to the bundled version of the Newtonsoft.Json.dll assembly being quite old (as of this writing, the bundled version is 8.0, whereas 12.0is current) and having a bug

    • The manifestation of this bug is the Self referencing loop detected for property 'Value' ... bug you saw.
  • In PowerShell [Core] v6+, the newer version of Newtonsoft.Json.dll that ships with PowerShell itself preempts the obsolete version, so the error doesn't occur, but the serialization problem becomes obvious:

    • The resulting { "CliXml": "<Objs Version=\"1.1.0.1\" .. } JSON text shows that the [pscustomobject] instance was serialized in CLIXML format, PowerShell's native XML-based serialization format, as notably used by PowerShell's remoting feature.

    • It is hypothetically possible - though very cumbersome - to manually deserialize such JSON by post-processing it and replacing objects with only a CliXml property with the return value from [System.Management.Automation.PSSerializer]::Deserialize()


Solutions:

  • If your intent is simply to compare serialized representations in a PS-edition-agnostic form, irrespective of the specific serialization format, consider using CLIXML directly, via Export-CliXml and Import-CliXml.

  • If you do want a PS-edition-agnostic way to serialize to JSON, you'll have to roll your own [pscustomobject]-to-ordered-hashtable converter, because serializing ordered hashtables ([ordered] @{ ... }, System.Collections.Specialized.OrderedDictionary) via Newtonsoft.Json does round-trip properly in PowerShell (it is, in fact, the data structure used by the ConvertFrom/To-JsonNewtonsoft wrapper cmdlets).

Both approaches are demonstrated in this related answer.

mklement0
  • 382,024
  • 64
  • 607
  • 775