0

I have an issue in PowerShell where a 1-element array returned from a function is not being returned as an array. I have found a number of places where similar questions have been asked(PowerShell: How can I to force to get a result as an Array instead of Object & How can I force Powershell to return an array when a call only returns one object?) but the solutions there don't work for me. Here's my code:

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    return @($levelMappings) 
}
(x).gettype().name

It returns

Hashtable

I don't want that, I want the thing that gets returned to always be an array even if it only has one element in it. I know i can get round this by explicitly converting the function's returned value to an array:

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    return @($levelMappings) 
}
@(x).gettype().name

but I don't want to have to do that. I want the function to reliably return an array each time.


The answers to an earlier draft of this question advised me to use the unary operator which worked a treat except that when I pipe the result to ConvertTo-Json (which is ultimately what I need to do) then i do't get the desired result. Observe:

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    $levelMappings += @{"e"="f";"g"="h"}
    return ,$levelMappings
}
(x).gettype().name
x | ConvertTo-Json

Upon running it the result is:

{
"value": [
{
"c": "d",
"a": "b"
},
{
"g": "h",
"e": "f"
}
],
"Count": 2
}

As you can see the object i'm serialising to JSON has been wrapped inside a JSON object called value and I now have a count object in there too. This isn't what I want. Any way to solve this?


Another update. I've discovered I can wrap the call to the function in parentheses and it seems to solve it.

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    $levelMappings += @{"e"="f";"g"="h"}
    return ,$levelMappings
}
(x).gettype().name
(x) | ConvertTo-Json

it returns

[
{
"c": "d",
"a": "b"
},
{
"g": "h",
"e": "f"
}
]

Its not ideal...but it does work.


Third update, no, that doesn't work. If its a 1-element array I'm back to the original problem:

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b"}
    return ,$levelMappings
}
(x).gettype().name
(x) | ConvertTo-Json

gives:

{
"a": "b"
}

I give up!!!


Fourth update...

Oh and if I chuck the returned value into a hashtable and convert that to json:

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b"}
    return ,$levelMappings
}
$hashtable = @{}
$hashtable.Add('somekey',(x))
$hashtable | ConvertTo-Json

it appears as a single-element JSON array!

{
"somekey": [
{
"a": "b"
}
]
}

Arrghh!!! :)

Community
  • 1
  • 1
jamiet
  • 10,501
  • 14
  • 80
  • 159
  • `return ,$levelMappings` – Etan Reisner Mar 18 '16 at 15:18
  • 1
    Possible duplicate of [Is there a way for a caller to get the output of a powershell function without subjecting it to (possible) pipeline unrolling?](http://stackoverflow.com/questions/28724537/is-there-a-way-for-a-caller-to-get-the-output-of-a-powershell-function-without-s) – Etan Reisner Mar 18 '16 at 15:19
  • Oh that's beautiful. Thank you very much. I did think that maybe it was a dupe but did plenty of searching and couldn't find anything. – jamiet Mar 18 '16 at 15:36
  • Possible duplicate of [How can I force Powershell to return an array when a call only returns one object?](http://stackoverflow.com/questions/11107428/how-can-i-force-powershell-to-return-an-array-when-a-call-only-returns-one-objec) – briantist Mar 18 '16 at 15:56
  • @briantist I did explain in the question how my problem isn't an exact duplicate of that – jamiet Mar 18 '16 at 16:12
  • @jamiet I see; close vote retracted. – briantist Mar 18 '16 at 16:13

4 Answers4

3

Try this:

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    return ,$levelMappings
}
(x).gettype().name

The unary comma operator is used to instruct PowerShell to wrap the object following it which will make the object an array and not a hashtable when returned.

boeprox
  • 1,848
  • 13
  • 24
  • Heh, you beat me to it, and with a better worded answer. – GodEater Mar 18 '16 at 15:26
  • It doesn't "make it an array" as much as it wraps the array in *another* array so that powershell unrolls *that* array instead of yours. Basically you are creating a throw-away array for powershell to mangle instead of your *real* array. – Etan Reisner Mar 18 '16 at 15:42
  • All, I've encountered a problem when piping to `ConvertTo-Json` and have edited the question accordingly. Would you mind taking a look? – jamiet Mar 18 '16 at 15:48
  • never mind, figured it out and have edited the question and posted an answer accordingly. – jamiet Mar 18 '16 at 15:58
1

Powershell does like to unroll arrays wherever it can.

You can force what you want by changing your return line to this :

return , $levelmappings

And it will pop out as an array.

GodEater
  • 3,445
  • 2
  • 27
  • 30
0

final answer, one that doesn't cause ConvertTo-Json to mess with the result

function x()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    $levelMappings += @{"e"="f";"g"="h"}
    return ,$levelMappings
}
(x).gettype().name
(x) | ConvertTo-Json
jamiet
  • 10,501
  • 14
  • 80
  • 159
  • Just realised that doesn't work if the array only has one element because PowerShell will unwrap it again! Sigh! So I'm back to where I started!! I give up :) – jamiet Mar 18 '16 at 16:07
  • consider refactoring your code to not use the pipeline, and perhaps your issue can be solved, and leave you with an improved pattern for other places in your code. – Kory Gill Mar 18 '16 at 19:24
0

Sometimes the pipeline is your friend, and sometimes you should just break your code into parts and be intentional, which could also help with readability down the road or when others read your code.

I searched on "powershell convertto-json force json array" and the first hit was: ConvertTo-JSON an array with a single item, and Ansgar Wiechers has posted an elegant answer there, so perhaps this is a duplicate of that. I suppose that depends on whether this question is really about "forcing a function to return an array" (the question) or whether it is really "force ConvertTo-Json to always create an array" (the problem).

Anyway, I believe the following accomplishes all the OP wishes do to:

Code:

function x1()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b"}
    return $levelMappings
}
"X1: " + (x1).gettype().name
ConvertTo-Json @(x1)

function x2()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    return $levelMappings
}
"X2: " + (x1).gettype().name
ConvertTo-Json @(x2)

function x3()
{
    $levelMappings = @()
    $levelMappings += @{"a"="b";"c"="d"}
    $levelMappings += @{"e"="f";"g"="h"}
    return $levelMappings
}
"X3: " + (x1).gettype().name
ConvertTo-Json @(x3)

Output:

X1: Hashtable
[
    {
        "a":  "b"
    }
]
X2: Hashtable
[
    {
        "c":  "d",
        "a":  "b"
    }
]
X3: Hashtable
[
    {
        "c":  "d",
        "a":  "b"
    },
    {
        "g":  "h",
        "e":  "f"
    }
]
Community
  • 1
  • 1
Kory Gill
  • 6,993
  • 1
  • 25
  • 33