Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Reflection;
Expand Down Expand Up @@ -160,45 +161,49 @@ public static object ConvertFromJson(string input, bool returnHashtable, int? ma
error = 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/1930.
// To work around this, we need to identify when input is a Json array, and then try to parse it via JArray.Parse().
var serializer = new JsonSerializer()
{
TypeNameHandling = TypeNameHandling.None,
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
MaxDepth = maxDepth
};

// If input starts with '[' (ignoring white spaces).
if (Regex.Match(input, @"^\s*\[").Success)
serializer.TypeNameHandling = TypeNameHandling.None;
serializer.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
serializer.MaxDepth = maxDepth;

using (System.IO.MemoryStream stream = new System.IO.MemoryStream(System.Text.Encoding.Default.GetBytes(input)))
using (System.IO.StreamReader streamReader = new System.IO.StreamReader(stream))
using (JsonReader reader = new JsonTextReader(streamReader))
{
// 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);
var readResult = reader.Read();

// Please note that if the Json array is valid, we don't do anything,
// we just continue the deserialization.
}
// If the first token in our file is an array let's read in that token so that our next token is an object or a primitive.
// This will allow newtonsoft to deserialize the incoming json one object at a time instead of doing the whole object at once.
if (reader.TokenType == JsonToken.StartArray)
{
List<object> result;

var obj = JsonConvert.DeserializeObject(
input,
new JsonSerializerSettings
result = DeserializeRootArrayHelper(reader, serializer, returnHashtable, out error);
if (error != null)
{
return null;
}

return result.ToArray();
}
else
{
// This TypeNameHandling setting is required to be secure.
TypeNameHandling = TypeNameHandling.None,
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
MaxDepth = maxDepth
});
object result;

switch (obj)
{
case JObject dictionary:
// JObject is a IDictionary
return returnHashtable
? PopulateHashTableFromJDictionary(dictionary, out error)
: PopulateFromJDictionary(dictionary, new DuplicateMemberHashSet(dictionary.Count), out error);
case JArray list:
return returnHashtable
? PopulateHashTableFromJArray(list, out error)
: PopulateFromJArray(list, out error);
default:
return obj;
result = DeserializeObjectHelper(reader, serializer, returnHashtable, out error);
if (error != null)
{
return null;
}

return result;
}
}
}
catch (JsonException je)
Expand All @@ -210,6 +215,56 @@ public static object ConvertFromJson(string input, bool returnHashtable, int? ma
}
}

private static List<object> DeserializeRootArrayHelper(JsonReader reader, JsonSerializer serializer, bool returnHashtable, out ErrorRecord error)
{
error = null;

List<object> elements = new List<object>();

while (reader.Read() && reader.TokenType != JsonToken.EndArray)
{
elements.Add(DeserializeObjectHelper(reader, serializer, returnHashtable, out error));
if (error != null)
{
return null;
}
}

if (reader.TokenType != JsonToken.EndArray)
{
var errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.JsonStringInBadFormat);
error = new ErrorRecord(
new InvalidOperationException(errorMsg),
"ArrayStartsButDoesNotEnd",
ErrorCategory.InvalidOperation,
null);
throw new ArgumentException(errorMsg);
}

return elements;
}

private static object DeserializeObjectHelper(JsonReader reader, JsonSerializer serializer, bool returnHashtable, out ErrorRecord error)
{
error = null;

var thisObject = serializer.Deserialize(reader);

switch (thisObject)
{
case JObject dictionary:
return returnHashtable
? PopulateHashTableFromJDictionary(dictionary, out error)
: PopulateFromJDictionary(dictionary, new DuplicateMemberHashSet(dictionary.Count), out error);
case JArray list:
return returnHashtable
? PopulateHashTableFromJArray(list, out error)
: PopulateFromJArray(list, out error);
default:
return thisObject;
}
}

// This function is a clone of PopulateFromDictionary using JObject as an input.
private static PSObject PopulateFromJDictionary(JObject entries, DuplicateMemberHashSet memberHashTracker, out ErrorRecord error)
{
Expand Down