Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Case insensitive ConvertFrom-Json -AsHashtable #19928

Open
UselessGuru opened this issue Jul 11, 2023 · 12 comments
Open

Case insensitive ConvertFrom-Json -AsHashtable #19928

UselessGuru opened this issue Jul 11, 2023 · 12 comments
Labels
Issue-Enhancement the issue is more of a feature request than a bug Needs-Triage The issue is new and needs to be triaged by a work group.

Comments

@UselessGuru
Copy link

UselessGuru commented Jul 11, 2023

Summary of the new feature / enhancement

Since 7.3, ConvertFrom-Json -AsHashtable returns an ordered hashtable that is ALWAYS case sensitive.

Sometimes I would like to have a case insensitive ordered hashtable.

Proposed technical implementation details (optional)

2 possible solutions:

  1. Add an extra switch like '-CaseInsensitive'
  2. Revert the 7.3 breaking change (by default hashtables are no longer case sensitive, as before 7.3) and add an optional switch '-CaseSensitive'
@UselessGuru UselessGuru added Issue-Enhancement the issue is more of a feature request than a bug Needs-Triage The issue is new and needs to be triaged by a work group. labels Jul 11, 2023
@iRon7

This comment was marked as resolved.

@UselessGuru
Copy link
Author

@iRon7

$Json = '{ "firstName": "Bill", "lastName": "Gates" }'
$OrderedHashTable = $Json | ConvertFrom-Json -AsHashTable
$HashTable = [HashTable]::New($OrderedHashTable)

With PWSH 7.3 the result is still a case sensitive hashtable:

$HashTable.FirstName
(nothing)
$HashTable.firstName
Bill

How can a (case insensitive) HashTable have both firstName and FirstName keys???

This should print an error, same as if you were to use ConvertFrom-Json without '-AsHashtable' to create a PSCustomObject.

@iRon7
Copy link

iRon7 commented Jul 14, 2023

@UselessGuru,
I guess, I didn't had a clear mind yesterday.
My misconception: although PowerShell hashtables (@{}) are case insensitive by default, that doesn't mean that the .Net constructor is case insensitive by default
For this you will need to set the [StringComparer]::OrdinalIgnoreCase:

$Json = '{ "firstName": "Bill", "lastName": "Gates" }'
$OrderedHashTable = $Json | ConvertFrom-Json -AsHashTable
$HashTable = [HashTable]::New($OrderedHashTable, [StringComparer]::OrdinalIgnoreCase)
$HashTable.FirstName = 'Bob'
$HashTable

Name                           Value
----                           -----
firstName                      Bob
lastName                       Gates

Anyways, I see this as a workaround. It is up to the PowerShell team to come up with a better answer/solution.

@iRon7
Copy link

iRon7 commented Jul 14, 2023

Additional thoughts:
Adding an extra -CaseInsensitive is somewhat confusing as most people might assume that -AsHashTable is already case insensitive as with other cmdlets and it was prior version 7.3. I think that two mutual exclusive switches: '-AsHashTableand-AsOrderedHashTable` whould make more sense.

  • But that would mean a break change.
    • But that appears to be already the case anyways with anything prior version 7.3 as suggested by this case.

@UselessGuru
Copy link
Author

Adding an extra -CaseInsensitive is somewhat confusing as most people might assume that -AsHashTable

I suggest reverting the 7.3 breaking change (by default hashtables are no longer case sensitive, as before 7.3)
then add an optional switch '-CaseSensitive'

@UselessGuru
Copy link
Author

$Json = '{ "firstName": "Bill", "lastName": "Gates" }'
$OrderedHashTable = $Json | ConvertFrom-Json -AsHashTable
$HashTable = [HashTable]::New($OrderedHashTable, [StringComparer]::OrdinalIgnoreCase)

This results in a non-ordered hash table 👎

@iRon7
Copy link

iRon7 commented Jul 14, 2023

@UselessGuru,

by default hashtables are no longer case sensitive

As a general principle, PowerShell is as case insensitive as possible while preserving case and not breaking the underlying OS.

Meaning PowerShell hashtables (@{}) are still case insensitive by default.
The only difference is that ConvertFrom-Json -AsHashTable no longer returns a pure (unordered case-insensitive) HashTable type but a (ordered case-sensitive) OrderedHashTable type which is derived from a HashTable:

$Json = '{ "firstName": "Bill", "lastName": "Gates" }'
$OrderedHashTable = $Json | ConvertFrom-Json -AsHashTable
$OrderedHashTable.PSTypeNames

System.Management.Automation.OrderedHashtable
System.Collections.Hashtable
System.Object

Afaik, the new OrderedHashTable class is just brought to existence to better support the foreign language Json (JavaScript) which is ordered and case sensitive.

This results in a non-ordered hash table 👎

Good point, unfortunately the new OrderedHashTable class doesn't have a StringComparer option (maybe something to consider for the team) and the OrderedDictionairy doesn't support a dictionary constructor.

@iRon7
Copy link

iRon7 commented Aug 21, 2023

@iSazonov,
Sorry for the shoutout to you (not sure if there is any other way for me to highlight issues), can this one be triaged?
I suspect an unintended break change in the OrderedHashTable implementation since version 7.3.
And fear that any solution to correct this, might introduce a break change on the current versions (after 7.3).
Thanks in advance.

@tehmichael
Copy link

I'm interested in a solution for this as well!

@iRon7
Copy link

iRon7 commented Nov 1, 2023

A way out for this issue might be a -As parameter (mutual exclusive with -AsHashTable) that supports multiple dictionary types, something like:

function ConvertFromJson {
    param(
        [Type]$As
    )
    if ($As.getInterfaces().Name -notcontains 'IDictionary') { Write-Error 'The type should be a dictionary type' }
    # foreach (recursive) associated array iteration {
        $Dicitionary = New-Object -TypeName $As # For C#, see: https://stackoverflow.com/q/752/1701026
    # }
}

ConvertFromJson -As HashTable

@iRon7
Copy link

iRon7 commented Jan 30, 2024

For what it is worth, I have created a tool set for object graphs.

This module treats all (nested) dictionary classes and PowerShell objects ([PSCustomObject]) as a "map node":
Meaning that it doesn't matter whether you start with ConvertFrom-Json or ConvertFrom-Json -AsHashTable

$Json = '{ "firstName": "Bill", "lastName": "Gates" }'
$Json |  ConvertFrom-Json | Get-Node 'firstname'

Path      Name      Depth Value
----      ----      ----- -----
firstName firstName     1 Bill

or

 $Json | ConvertFrom-Json -AsHashTable | Get-Node ...

It will find the same map node (based on the actual dictionary comparer).

Besides it includes Copy-ObjectGraph might be useful for this specific issue where you might easily convert any (recursive) Map Node (meaning a dictionary type node or a [PSCustomObject]/object type node) to any other Map Node:

Install-Module -Name ObjectGraphTools

$Json = '{ "firstName": "Bill", "lastName": "Gates" }'
$OrderedHashTable = $Json | ConvertFrom-Json | Copy-ObjectGraph -MapAs ([Ordered]@{})

$OrderedHashTable.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     OrderedDictionary                        System.Object

@ArmaanMcleod
Copy link
Contributor

I do feel like for a lot of users including myself, having this "by design" feature makes things awkward.

If you are deserializing JSON and don't care about case sensitive keys being preserved, you have to recursively update all nested hash tables to be case insensitive again which is just annoying especially when dealing with large JSON documents.

I personally would be fine with bringing in a -CaseInsensitive switch so there is at least an option to opt out of the case sensitive behaviour.

One example that has gotten really annoying is Azure function apps using PowerShell:

param($EventGridEvent, $TriggerMetadata)

# Function app code
$topic = $EventGridEvent.topic

Where $EventGridEvent is the JSON event deserialized to a hashtable which is using ConvertFrom-Json -AsHashtable in the background.

Now I need to recursively update all nested hashtables to be case insensitive or risk having my code break trying to access a property, e.g. $EventGridEvent.topic instead of $EventGridEvent.Topic.

Now someone could tell me to just get the case right to begin with, but Microsoft breaks this all the time sending new version of event payloads with different cases and as a user I'm left in the dark and risking my code just breaking because it could not access a property.

I could avoid this just having the ability to make all nested hashtables case insensitive, which now I am forced to write a recursive workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Enhancement the issue is more of a feature request than a bug Needs-Triage The issue is new and needs to be triaged by a work group.
Projects
None yet
Development

No branches or pull requests

4 participants