Two issues at play here. First, an empty pipeline is not the same thing as $null, as explained in "Why is an empty PowerShell pipeline not the same as null?" @{test=@() | Where-Object {$_ -ne $null}} produces a hash table with a test key that has an AutomationNull value, not $null.
Second, there's a bug in PowerShell prior to 7.0 where AutomationNull is serialized as an empty object rather than $null, as reported in "ConvertTo-Json unexpectedly serializes AutomationValue.Null as an empty object, not null" (powershell#9231).
To work around this bug in older versions, you can coerce the AutomationNull to a real $null by inserting a psobject cast, which will then result in the value serializing properly:
@{test=[psobject](@() | Where-Object {$_ -ne $null})} | ConvertTo-Json -Compres