The JavaScriptSerializer from the .NET framework also has a similar problem with serializing PowerShell's hashes. I suspect it something slightly odd in the PowerShell type system. You could skip Json.Net altogether and roll your own.
Below is something to start you off. It's likely not as robust as PowerShell 3's built-in ConvertTo-Json cmdlet, but I think it's mostly complete.
Here are all of your examples, in working order.
# See below for ConvertTo-Json.psm1
Import-Module ConvertTo-Json
function Foo($a, $b)
{
$o = @{}
$o.A = $a
$o.B = $b
$post = @{}
$post.X="x"
$post.entity =$o
ConvertTo-Json $post
}
function Foo2($o)
{
$post = @{}
$post.X="x"
$post.entity =$o
ConvertTo-Json $post
}
function foo3($a, $b)
{
$o = @{}
$o.A = $a
$o.B = $b
ConvertTo-Json $o
}
PS> foo "a" "b"
{"entity":{"A":"a","B":"b"},"X":"x"}
PS> foo2 @{a="a"; b="b"}
{"entity":{"a":"a","b":"b"},"X":"x"}
PS> foo3 "a" "b"
{"A":"a","B":"b"}
PS> foo3 "a" 1
{"A":"a","B":1}
And here's the PowerShell module that implements ConvertTo-Json.
# Save these contents to Modules\ConvertTo-Json\ConvertTo-Json.psm1 in your
# PowerShell documents folder, and load them in your $profile using the
# "Import-Module ConvertTo-Json" cmdlet. This will make the ConvertTo-Json cmdlet
# available for use.
Set-StrictMode -Version Latest
function convertToJsonNull($InputObject) {
"null"
}
function convertToJsonArray($InputObject) {
$value = ($InputObject | %{ convertToJson $_ }) -join ','
"[$value]"
}
function convertToJsonHash($InputObject) {
$value = ($InputObject.Keys | %{
$name = $_ | asJsonString
$itemValue = convertToJson ($InputObject[$_])
'"{0}":{1}' -f $name, $itemValue
}) -join ','
"{$value}"
}
function convertToJsonObject($InputObject) {
$value = ($InputObject | get-member -membertype *property | %{
$name = $_.Name
$value = convertToJson ($InputObject.($name))
'"{0}":{1}' -f ($name | asJsonString), $value
}) -join ','
"{$value}"
}
function convertToJsonString($InputObject) {
'"{0}"' -f ($InputObject | asJsonString)
}
function convertToJsonBool($InputObject) {
$InputObject.ToString().ToLower()
}
function convertToJsonNumeric($InputObject) {
"$InputObject"
}
function convertToJsonDate($InputObject) {
$epoch = [datetime]"1970-01-01T00:00:00Z"
$elapsed = [long]($InputObject - $epoch).TotalMilliseconds
'"\/Date({0})\/"' -f $elapsed
}
filter isNumeric() {
$_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] -or
$_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] -or
$_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
}
filter asJsonString {
($_ -replace '\\', '\\') -replace '"', '\"'
}
function convertToJson($InputObject) {
if ($InputObject -eq $null) { convertToJsonNull $InputObject }
elseif ($InputObject -is [array]) { convertToJsonArray $InputObject }
elseif ($InputObject -is [hashtable]) { convertToJsonHash $InputObject }
elseif ($InputObject -is [datetime]) { convertToJsonDate $InputObject }
elseif ($InputObject -is [string]) { convertToJsonString $InputObject }
elseif ($InputObject -is [char]) { convertToJsonString $InputObject }
elseif ($InputObject -is [bool]) { convertToJsonBool $InputObject }
elseif ($InputObject | isNumeric) { convertToJsonNumeric $InputObject }
else { convertToJsonObject $InputObject }
}
function ConvertTo-Json {
[CmdletBinding()]
param(
[Parameter(
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true
)]
$InputObject
)
convertToJson $InputObject
}
Export-ModuleMember -Function ConvertTo-Json