mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 12:45:47 +00:00
329 lines
13 KiB
C#
329 lines
13 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Management.Automation;
|
|
using System.Text.RegularExpressions;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace Microsoft.PowerShell.Commands
|
|
{
|
|
/// <summary>
|
|
/// JsonObject class.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
|
|
public static class JsonObject
|
|
{
|
|
private class DuplicateMemberHashSet : HashSet<string>
|
|
{
|
|
public DuplicateMemberHashSet(int capacity) : base(capacity, StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a Json string back to an object of type PSObject.
|
|
/// </summary>
|
|
/// <param name="input">The json text to convert.</param>
|
|
/// <param name="error">An error record if the conversion failed.</param>
|
|
/// <returns>A PSObject.</returns>
|
|
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
|
|
public static object ConvertFromJson(string input, out ErrorRecord error)
|
|
{
|
|
return ConvertFromJson(input, returnHashtable: false, out error);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a Json string back to an object of type <see cref="System.Management.Automation.PSObject"/> or
|
|
/// <see cref="System.Collections.Hashtable"/> depending on parameter <paramref name="returnHashtable"/>.
|
|
/// </summary>
|
|
/// <param name="input">The json text to convert.</param>
|
|
/// <param name="returnHashtable">True if the result should be returned as a <see cref="System.Collections.Hashtable"/>
|
|
/// instead of a <see cref="System.Management.Automation.PSObject"/>.</param>
|
|
/// <param name="error">An error record if the conversion failed.</param>
|
|
/// <returns>A <see cref="System.Management.Automation.PSObject"/> or a <see cref="System.Collections.Hashtable"/>
|
|
/// if the <paramref name="returnHashtable"/> parameter is true.</returns>
|
|
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
|
|
public static object ConvertFromJson(string input, bool returnHashtable, out ErrorRecord error)
|
|
{
|
|
if (input == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(input));
|
|
}
|
|
|
|
error = null;
|
|
try
|
|
{
|
|
var obj = JsonConvert.DeserializeObject(
|
|
input,
|
|
new JsonSerializerSettings
|
|
{
|
|
TypeNameHandling = TypeNameHandling.None,
|
|
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
|
|
MaxDepth = 1024
|
|
});
|
|
|
|
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;
|
|
}
|
|
}
|
|
catch (JsonException je)
|
|
{
|
|
var msg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.JsonDeserializationFailed, je.Message);
|
|
|
|
// the same as JavaScriptSerializer does
|
|
throw new ArgumentException(msg, je);
|
|
}
|
|
}
|
|
|
|
// This function is a clone of PopulateFromDictionary using JObject as an input.
|
|
private static PSObject PopulateFromJDictionary(JObject entries, DuplicateMemberHashSet memberHashTracker, out ErrorRecord error)
|
|
{
|
|
error = null;
|
|
var result = new PSObject(entries.Count);
|
|
foreach (var entry in entries)
|
|
{
|
|
if (string.IsNullOrEmpty(entry.Key))
|
|
{
|
|
var errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.EmptyKeyInJsonString);
|
|
error = new ErrorRecord(
|
|
new InvalidOperationException(errorMsg),
|
|
"EmptyKeyInJsonString",
|
|
ErrorCategory.InvalidOperation,
|
|
null);
|
|
return null;
|
|
}
|
|
|
|
// Case sensitive duplicates should normally not occur since JsonConvert.DeserializeObject
|
|
// does not throw when encountering duplicates and just uses the last entry.
|
|
if (memberHashTracker.TryGetValue(entry.Key, out var maybePropertyName)
|
|
&& string.Compare(entry.Key, maybePropertyName, StringComparison.CurrentCulture) == 0)
|
|
{
|
|
var errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.DuplicateKeysInJsonString, entry.Key);
|
|
error = new ErrorRecord(
|
|
new InvalidOperationException(errorMsg),
|
|
"DuplicateKeysInJsonString",
|
|
ErrorCategory.InvalidOperation,
|
|
null);
|
|
return null;
|
|
}
|
|
|
|
// Compare case insensitive to tell the user to use the -AsHashTable option instead.
|
|
// This is because PSObject cannot have keys with different casing.
|
|
if (memberHashTracker.TryGetValue(entry.Key, out var propertyName))
|
|
{
|
|
var errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.KeysWithDifferentCasingInJsonString, propertyName, entry.Key);
|
|
error = new ErrorRecord(
|
|
new InvalidOperationException(errorMsg),
|
|
"KeysWithDifferentCasingInJsonString",
|
|
ErrorCategory.InvalidOperation,
|
|
null);
|
|
return null;
|
|
}
|
|
|
|
// Array
|
|
switch (entry.Value)
|
|
{
|
|
case JArray list:
|
|
{
|
|
var listResult = PopulateFromJArray(list, out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result.Properties.Add(new PSNoteProperty(entry.Key, listResult));
|
|
break;
|
|
}
|
|
case JObject dic:
|
|
{
|
|
// Dictionary
|
|
var dicResult = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result.Properties.Add(new PSNoteProperty(entry.Key, dicResult));
|
|
break;
|
|
}
|
|
case JValue value:
|
|
{
|
|
result.Properties.Add(new PSNoteProperty(entry.Key, value.Value));
|
|
break;
|
|
}
|
|
}
|
|
|
|
memberHashTracker.Add(entry.Key);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// This function is a clone of PopulateFromList using JArray as input.
|
|
private static ICollection<object> PopulateFromJArray(JArray list, out ErrorRecord error)
|
|
{
|
|
error = null;
|
|
var result = new object[list.Count];
|
|
|
|
for (var index = 0; index < list.Count; index++)
|
|
{
|
|
var element = list[index];
|
|
switch (element)
|
|
{
|
|
case JArray subList:
|
|
{
|
|
// Array
|
|
var listResult = PopulateFromJArray(subList, out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result[index] = listResult;
|
|
break;
|
|
}
|
|
case JObject dic:
|
|
{
|
|
// Dictionary
|
|
var dicResult = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result[index] = dicResult;
|
|
break;
|
|
}
|
|
case JValue value:
|
|
{
|
|
result[index] = value.Value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// This function is a clone of PopulateFromDictionary using JObject as an input.
|
|
private static Hashtable PopulateHashTableFromJDictionary(JObject entries, out ErrorRecord error)
|
|
{
|
|
error = null;
|
|
Hashtable result = new Hashtable(entries.Count);
|
|
foreach (var entry in entries)
|
|
{
|
|
// Case sensitive duplicates should normally not occur since JsonConvert.DeserializeObject
|
|
// does not throw when encountering duplicates and just uses the last entry.
|
|
if (result.ContainsKey(entry.Key))
|
|
{
|
|
string errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.DuplicateKeysInJsonString, entry.Key);
|
|
error = new ErrorRecord(
|
|
new InvalidOperationException(errorMsg),
|
|
"DuplicateKeysInJsonString",
|
|
ErrorCategory.InvalidOperation,
|
|
null);
|
|
return null;
|
|
}
|
|
|
|
switch (entry.Value)
|
|
{
|
|
case JArray list:
|
|
{
|
|
// Array
|
|
var listResult = PopulateHashTableFromJArray(list, out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result.Add(entry.Key, listResult);
|
|
break;
|
|
}
|
|
case JObject dic:
|
|
{
|
|
// Dictionary
|
|
var dicResult = PopulateHashTableFromJDictionary(dic, out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result.Add(entry.Key, dicResult);
|
|
break;
|
|
}
|
|
case JValue value:
|
|
{
|
|
result.Add(entry.Key, value.Value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// This function is a clone of PopulateFromList using JArray as input.
|
|
private static ICollection<object> PopulateHashTableFromJArray(JArray list, out ErrorRecord error)
|
|
{
|
|
error = null;
|
|
var result = new object[list.Count];
|
|
|
|
for (var index = 0; index < list.Count; index++)
|
|
{
|
|
var element = list[index];
|
|
|
|
switch (element)
|
|
{
|
|
case JArray array:
|
|
{
|
|
// Array
|
|
var listResult = PopulateHashTableFromJArray(array, out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result[index] = listResult;
|
|
break;
|
|
}
|
|
case JObject dic:
|
|
{
|
|
// Dictionary
|
|
var dicResult = PopulateHashTableFromJDictionary(dic, out error);
|
|
if (error != null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result[index] = dicResult;
|
|
break;
|
|
}
|
|
case JValue value:
|
|
{
|
|
result[index] = value.Value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|