From 7aa7f3858cf41e4474b07fd4ed4b0b2f25fdb831 Mon Sep 17 00:00:00 2001 From: Francisco Gamino Date: Thu, 25 May 2017 13:00:51 -0700 Subject: [PATCH] Make ConvertFrom-Json deserialize an array of objects with multiple lines. (#3823) * Fixing ConvertFrom-Json on CoreCLR to be able to handle a collection of strings which represent a JSON content. * Adding test case for ConvertFrom-Json to process an array of PSObjects as a single string. --- .../WebCmdlet/ConvertFromJsonCommand.cs | 6 +++-- .../commands/utility/WebCmdlet/JsonObject.cs | 18 +++++++++++++++ .../Pester.Commands.Cmdlets.Json.Tests.ps1 | 22 ++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs index 27b45df88..9ec8ef961 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs @@ -72,8 +72,8 @@ namespace Microsoft.PowerShell.Commands protected override void EndProcessing() { // When Input is provided through pipeline, the input can be represented in the following two ways: - // 1. Each input to the buffer is a complete Json content. There can be multiple inputs of this format. - // 2. The complete buffer input collectively represent a single JSon format. This is typically the majority of the case. + // 1. Each input in the collection is a complete Json content. There can be multiple inputs of this format. + // 2. The complete input is a collection which represents a single Json content. This is typically the majority of the case. if (_inputObjectBuffer.Count > 0) { if (_inputObjectBuffer.Count == 1) @@ -85,6 +85,7 @@ namespace Microsoft.PowerShell.Commands bool successfullyConverted = false; try { + // Try to deserialize the first element. successfullyConverted = ConvertFromJsonHelper(_inputObjectBuffer[0]); } catch (ArgumentException) @@ -102,6 +103,7 @@ namespace Microsoft.PowerShell.Commands } else { + // Process the entire input as a single Json content. ConvertFromJsonHelper(string.Join(System.Environment.NewLine, _inputObjectBuffer.ToArray())); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs index 87fbe079e..6561c4699 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Globalization; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; +using System.Text.RegularExpressions; #if CORECLR using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -42,11 +43,28 @@ namespace Microsoft.PowerShell.Commands { throw new ArgumentNullException("input"); } + error = null; #if CORECLR object obj = null; try { + // JsonConvert.DeserializeObject does not throw an exception when an invalid Json array is passed. + // This issue is being tracked by https://github.com/JamesNK/Newtonsoft.Json/issues/1321. + // To work around this, we need to identify when input is a Json array, and then try to parse it via JArray.Parse(). + + // If input starts with '[' or ends with ']' (ignoring white spaces). + if ((Regex.Match(input, @"(^\s*\[)|(\s*\]$)")).Success) + { + // JArray.Parse() will throw a JsonException if the array is invalid. + // This will be caught by the catch block below, and then throw an + // ArgumentException - this is done to have same behavior as the JavaScriptSerializer. + JArray.Parse(input); + + // Please note that if the Json array is valid, we don't do anything, + // we just continue the deserialization. + } + obj = JsonConvert.DeserializeObject(input, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.None, MaxDepth = 1024 }); // JObject is a IDictionary diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Pester.Commands.Cmdlets.Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Pester.Commands.Cmdlets.Json.Tests.ps1 index b2feb204c..666f5463d 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Pester.Commands.Cmdlets.Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Pester.Commands.Cmdlets.Json.Tests.ps1 @@ -228,7 +228,6 @@ Describe "Json Tests" -Tags "Feature" { # add a ScriptProperty called IsOld which returns whether the version is an older version $versionObject | Add-Member -MemberType ScriptProperty -Name IsOld -Value { ($this.Major -le 3) } - $jstr = ConvertTo-Json $versionObject # convert the JSON string to a JSON object @@ -1455,4 +1454,25 @@ Describe "Json Bug fixes" -Tags "Feature" { { RunJsonTest $testCase } + + It "ConvertFrom-Json deserializes an array of PSObjects (in multiple lines) as a single string." { + + # Create an array of PSCustomObjects, and serialize it + $array = [pscustomobject]@{ objectName = "object1Name"; objectValue = "object1Value" }, + [pscustomobject]@{ objectName = "object2Name"; objectValue = "object2Value" } + + # Serialize the array to a text file + $filePath = Join-Path $TESTDRIVE test.json + $array | ConvertTo-Json | Out-File $filePath -Encoding utf8 + + # Read the object as an array of PSObjects and deserialize it. + $result = Get-Content $filePath | ConvertFrom-Json + $result.Count | Should be 2 + } + + It "ConvertFrom-Json deserializes an array of strings (in multiple lines) as a single string." { + + $result = "[1,","2,","3]" | ConvertFrom-Json + $result.Count | Should be 3 + } }