Search code examples
c#powershellhjson

HJSON C# library for PowerShell


I'm trying to use HJSON C# library for PowerShell: https://github.com/hjson/hjson-cs I've successfully compiled the dll, put it into a folder and added the type via standard procedure:

#LOAD
$scriptRoot = Split-Path $script:psEditor.GetEditorContext().CurrentFile.Path

$FilePath = ( Get-Item .\Hjson.dll ).FullName
[System.Reflection.Assembly]::LoadFrom("$FilePath")

[Hjson.IJsonReader]::new()

[Hjson.hjson]::Load("$scriptRoot\test.hjson")

I'm trying to follow examples to get the basics:
Read method: https://github.com/hjson/hjson-cs#read

# var jsonObject = HjsonValue.Load(filePath).Qo();

$jsonTempData = [Hjson.HjsonValue]::Load("$scriptRoot\test.hjson")
$jsonObject = [Hjson.JsonUtil]::Qo($jsonTempData)
$jsonObject

but the output is missing values:

PS D:\OneDrive\PS-HJSON> $jsonObject

Key       Value
---       -----
hello
text
quote
otherwise
abc-123
commas
but
trailing
multiline
number
negative
yes
no
null
array
array2


PS D:\OneDrive\PS-HJSON>

So I can't see values. Why it doesn't work like JSON objects?

And when I try to iterate through keys:

foreach ( $item in $jsonObject) {
    $item.Key, $item.Value
}

I got this:

The following exception occurred while trying to enumerate the collection: "The operation is invalid due to the current state of the object. "

I'm sure I'm missing something, but I don't know c# enough to know what to do.


Solution

  • The library is just written in a way that doesn't work great with how PowerShell displays data it doesn't have format information for.

    Why

    • JsonValue (the type emitted by Hjson.Load) is more or less a dictionary of string to JsonPrimitive (or more JsonValue for nesting).

    • The reason you don't see any values when you output the variable is because PowerShell by default just converts objects to a string. The JsonValue to string conversion is just an empty string, so it appears like a null value, but it's a full object.

    • The reason it throws the InvalidOperationException referencing enumeration is because PowerShell tries to enumerate anything that implements IEnumerable. But, JsonPrimitive will throw when you try to enumerate it if the real value of the object isn't an array.

    Solution

    Individual values

    If you want to get an individual value, you can call the JsonPrimitive.ToValue method. This will convert the JsonPrimitive to the equivalent .NET type.

    $jsonObject = [Hjson.HjsonValue]::Load("myFile.hjson")
    $jsonObject['MyKey'].ToValue()
    

    The problem with this is it will only work on keys you know are primitives. That means to do a full conversion to a normal displayable type you would have to enumerate the JsonValue, check if it's a JsonPrimitive or JsonObject, then either call ToValue or recurse into the nested object.

    Full conversion

    A simpler approach might be to just convert it to json as PowerShell is a lot better at handling that

    $jsonObject = [Hjson.HjsonValue]::Load("myFile.hjson")
    $stringWriter = [System.IO.StringWriter]::new()
    $jsonObject.Save($stringWriter, [Hjson.Stringify]::Plain)
    $hjsonAsPSObject = $stringWriter.GetStringBuilder().ToString() | ConvertFrom-Json
    

    The Save method takes a path, a stream, or a TextWriter. The StringWriter object is just an easy way to get a string from something that accepts a TextWriter.

    How to tell?

    If you're ever faced with an object that you think should have a value but displays like it doesn't have a value, there's a solid chance it just doesn't display right in PowerShell. In this scenario, the easiest way you could have tested would be trying some of these:

    # Shows you the objects type, or if it really is $null it will throw
    $jsonObject['jsonKey'].GetType()
    
    # This will tell you the properties and methods available on an object
    # but in this case it would throw due to the enumeration funk.
    $jsonObject['jsonKey'] | Get-Member
    
    # This gets around the enumeration issue.  Because there's no pipeline,
    # Get-Member gives you the members for the type before enumeration.
    Get-Member -InputObject ($jsonObject['jsonKey'])