Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ internal virtual void ValidateParameters()
{
if (Directory.Exists(providerPaths[0]))
{
errorRecord = GetValidationError(WebCmdletStrings.DirecotryPathSpecified,
errorRecord = GetValidationError(WebCmdletStrings.DirectoryPathSpecified,
"WebCmdletInFileNotFilePathException", InFile);
}
_originalFilePath = InFile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,23 @@ public class ConvertFromJsonCommand : Cmdlet
#region parameters

/// <summary>
/// gets or sets the InputString property
/// Gets or sets the InputString property.
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
[AllowEmptyString]
public string InputObject { get; set; }

/// <summary>
/// inputObjectBuffer buffers all InputObjet contents available in the pipeline.
/// InputObjectBuffer buffers all InputObject contents available in the pipeline.
/// </summary>
private List<string> _inputObjectBuffer = new List<string>();

/// <summary>
/// Returned data structure is a Hashtable instead a CustomPSObject.
/// </summary>
[Parameter()]
public SwitchParameter AsHashtable { get; set; }

#endregion parameters

#region overrides
Expand All @@ -44,7 +50,7 @@ protected override void ProcessRecord()
}

/// <summary>
/// the main execution method for the convertfrom-json command
/// The main execution method for the ConvertFrom-Json command.
/// </summary>
protected override void EndProcessing()
{
Expand Down Expand Up @@ -95,7 +101,7 @@ protected override void EndProcessing()
private bool ConvertFromJsonHelper(string input)
{
ErrorRecord error = null;
object result = JsonObject.ConvertFromJson(input, out error);
object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, out error);

if (error != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Management.Automation.Internal;
using System.Reflection;

Expand All @@ -26,13 +28,26 @@ public static class JsonObject
private const int maxDepthAllowed = 100;

/// <summary>
/// Convert a Json string back to an object
/// Convert a Json string back to an object of type PSObject.
/// </summary>
/// <param name="input"></param>
/// <param name="error"></param>
/// <returns></returns>
/// <returns>A PSObject.</returns>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
public static object ConvertFromJson(string input, out ErrorRecord error)
{
return ConvertFromJson(input, false, out error);
}

/// <summary>
/// Convert a Json string back to an object of type PSObject or Hashtable depending on parameter <paramref name="returnHashTable"/>.
/// </summary>
/// <param name="input"></param>
/// <param name="returnHashTable"></param>
/// <param name="error"></param>
/// <returns>A PSObject or a 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)
{
Expand Down Expand Up @@ -65,15 +80,29 @@ public static object ConvertFromJson(string input, out ErrorRecord error)
var dictionary = obj as JObject;
if (dictionary != null)
{
obj = PopulateFromJDictionary(dictionary, out error);
if (returnHashTable)
{
obj = PopulateHashTableFromJDictionary(dictionary, out error);
}
else
{
obj = PopulateFromJDictionary(dictionary, out error);
}
}
else
{
// JArray is a collection
var list = obj as JArray;
if (list != null)
{
obj = PopulateFromJArray(list, out error);
if (returnHashTable)
{
obj = PopulateHashTableFromJArray(list, out error);
}
else
{
obj = PopulateFromJArray(list, out error);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add 'else' with Diagnostics.Assert (if the object is not JObject or JArray).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the cast to JArray a few lines above, obj cannot be something else. I initially put your suggestion in but even the compiler tells me that:
error CS0184: The given expression is never of the provided ('JObject') type

Copy link
Collaborator

@iSazonov iSazonov Oct 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant:

                        Diagnostics.Assert(
                            false,
                            "Only JObject or JArray is supported.");

}
}
Expand All @@ -93,14 +122,42 @@ private static PSObject PopulateFromJDictionary(JObject entries, out ErrorRecord
PSObject result = new PSObject();
foreach (var entry in entries)
{
if (string.IsNullOrEmpty(entry.Key))
{
string errorMsg = string.Format(CultureInfo.InvariantCulture,
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 (result.Properties.Any(psPropertyInfo => psPropertyInfo.Name.Equals(entry.Key, StringComparison.InvariantCulture)))
{
string errorMsg = string.Format(CultureInfo.InvariantCulture,
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.
PSPropertyInfo property = result.Properties[entry.Key];
if (property != null)
{
string errorMsg = string.Format(CultureInfo.InvariantCulture,
WebCmdletStrings.DuplicateKeysInJsonString, property.Name, entry.Key);
WebCmdletStrings.KeysWithDifferentCasingInJsonString, property.Name, entry.Key);
error = new ErrorRecord(
new InvalidOperationException(errorMsg),
"DuplicateKeysInJsonString",
"KeysWithDifferentCasingInJsonString",
ErrorCategory.InvalidOperation,
null);
return null;
Expand Down Expand Up @@ -180,5 +237,101 @@ private static ICollection<object> PopulateFromJArray(JArray list, out ErrorReco
}
return result.ToArray();
}

// 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();
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.InvariantCulture,
WebCmdletStrings.DuplicateKeysInJsonString, entry.Key);
error = new ErrorRecord(
new InvalidOperationException(errorMsg),
"DuplicateKeysInJsonString",
ErrorCategory.InvalidOperation,
null);
return null;
}

// Array
else if (entry.Value is JArray)
{
JArray list = entry.Value as JArray;
ICollection<object> listResult = PopulateHashTableFromJArray(list, out error);
if (error != null)
{
return null;
}
result.Add(entry.Key, listResult);
}

// Dictionary
else if (entry.Value is JObject)
{
JObject dic = entry.Value as JObject;
Hashtable dicResult = PopulateHashTableFromJDictionary(dic, out error);
if (error != null)
{
return null;
}
result.Add(entry.Key, dicResult);
}

// Value
else // (entry.Value is JValue)
{
JValue theValue = entry.Value as JValue;
result.Add(entry.Key, theValue.Value);
}
}
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;
List<object> result = new List<object>();

foreach (var element in list)
{
// Array
if (element is JArray)
{
JArray subList = element as JArray;
ICollection<object> listResult = PopulateHashTableFromJArray(subList, out error);
if (error != null)
{
return null;
}
result.Add(listResult);
}

// Dictionary
else if (element is JObject)
{
JObject dic = element as JObject;
Hashtable dicResult = PopulateHashTableFromJDictionary(dic, out error);
if (error != null)
{
return null;
}
result.Add(dicResult);
}

// Value
else // (element is JValue)
{
result.Add(((JValue)element).Value);
}
}
return result.ToArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,17 @@
<data name="CredentialConflict" xml:space="preserve">
<value>The cmdlet cannot run because the following conflicting parameters are specified: Credential and UseDefaultCredentials. Specify either Credential or UseDefaultCredentials, then retry.</value>
</data>
<data name="DirecotryPathSpecified" xml:space="preserve">
<data name="DirectoryPathSpecified" xml:space="preserve">
<value>Path '{0}' resolves to a directory. Specify a path including a file name, and then retry the command.</value>
</data>
<data name="EmptyKeyInJsonString" xml:space="preserve">
<value>The provided JSON includes a property whose name is an empty string, this is only supported using the -AsHashTable switch.</value>
</data>
<data name="DuplicateKeysInJsonString" xml:space="preserve">
<value>Cannot convert the JSON string because a dictionary that was converted from the string contains the duplicated keys '{0}' and '{1}'.</value>
<value>Cannot convert the JSON string because a dictionary that was converted from the string contains the duplicated key '{0}'.</value>
</data>
<data name="KeysWithDifferentCasingInJsonString" xml:space="preserve">
<value>Cannot convert the JSON string because it contains keys with different casing. Please use the -AsHashTable switch instead. The key that was attempted to be added to the exisiting key '{0}' was '{1}'.</value>
</data>
<data name="ExtendedProfileRequired" xml:space="preserve">
<value>The ConvertTo-Json and ConvertFrom-Json cmdlets require the installation of the .NET Client Profile, sometimes called the .NET extended profile.</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
Describe 'ConvertFrom-Json' -tags "CI" {
It 'can convert a single-line object' {
('{"a" : "1"}' | ConvertFrom-Json).a | Should Be 1

BeforeAll {
$testCasesWithAndWithoutAsHashtableSwitch = @(
@{ AsHashtable = $true }
@{ AsHashtable = $false }
)
}

It 'can convert one string-per-object' {
$json = @('{"a" : "1"}', '{"a" : "x"}') | ConvertFrom-Json

It 'Can convert a single-line object with AsHashtable switch set to <AsHashtable>' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
('{"a" : "1"}' | ConvertFrom-Json -AsHashtable:$AsHashtable).a | Should Be 1
}

It 'Can convert one string-per-object with AsHashtable switch set to <AsHashtable>' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
$json = @('{"a" : "1"}', '{"a" : "x"}') | ConvertFrom-Json -AsHashtable:$AsHashtable
$json.Count | Should Be 2
$json[1].a | Should Be 'x'
if ($AsHashtable)
{
$json | Should BeOfType Hashtable
}
}

It 'can convert multi-line object' {
$json = @('{"a" :', '"x"}') | ConvertFrom-Json
It 'Can convert multi-line object with AsHashtable switch set to <AsHashtable>' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
$json = @('{"a" :', '"x"}') | ConvertFrom-Json -AsHashtable:$AsHashtable
$json.a | Should Be 'x'
if ($AsHashtable)
{
$json | Should BeOfType Hashtable
}
}
}
Loading