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 @@ -107,7 +107,15 @@ protected override void EndProcessing()
object objectToProcess = (_inputObjects.Count > 1) ? (_inputObjects.ToArray() as object) : (_inputObjects[0]);
// Pre-process the object so that it serializes the same, except that properties whose
// values cannot be evaluated are treated as having the value null.
object preprocessedObject = ProcessValue(objectToProcess, 0);
object preprocessedObject = null;
try
{
preprocessedObject = ProcessValue(objectToProcess, 0);
}
catch (StoppingException)
{
return;
}
JsonSerializerSettings jsonSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, MaxDepth = 1024 };
if (EnumsAsStrings)
{
Expand All @@ -124,272 +132,6 @@ protected override void EndProcessing()

#endregion overrides

#region convertOutputToPrettierFormat

/// <summary>
/// Convert the Json string to a more readable format
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
private string ConvertToPrettyJsonString(string json)
{
if (!json.StartsWith("{", StringComparison.OrdinalIgnoreCase) && !json.StartsWith("[", StringComparison.OrdinalIgnoreCase))
{
return json;
}

StringBuilder retStr = new StringBuilder();
if (json.StartsWith("{", StringComparison.OrdinalIgnoreCase))
{
retStr.Append('{');
ConvertDictionary(json, 1, retStr, "", 0);
}
else if (json.StartsWith("[", StringComparison.OrdinalIgnoreCase))
{
retStr.Append('[');
ConvertList(json, 1, retStr, "", 0);
}

return retStr.ToString();
}

/// <summary>
/// Convert a Json List, which starts with '['.
/// </summary>
/// <param name="json"></param>
/// <param name="index"></param>
/// <param name="result"></param>
/// <param name="padString"></param>
/// <param name="numberOfSpaces"></param>
/// <returns></returns>
private int ConvertList(string json, int index, StringBuilder result, string padString, int numberOfSpaces)
{
result.Append("\r\n");
StringBuilder newPadString = new StringBuilder();
newPadString.Append(padString);
AddSpaces(numberOfSpaces, newPadString);
AddIndentations(1, newPadString);

bool headChar = true;

for (int i = index; i < json.Length; i++)
{
switch (json[i])
{
case '{':
result.Append(newPadString.ToString());
result.Append(json[i]);
i = ConvertDictionary(json, i + 1, result, newPadString.ToString(), 0);
headChar = false;
break;
case '[':
result.Append(newPadString.ToString());
result.Append(json[i]);
i = ConvertList(json, i + 1, result, newPadString.ToString(), 0);
headChar = false;
break;
case ']':
result.Append("\r\n");
result.Append(padString);
AddSpaces(numberOfSpaces, result);
result.Append(json[i]);
return i;
case '"':
if (headChar)
{
result.Append(newPadString.ToString());
}
result.Append(json[i]);
i = ConvertQuotedString(json, i + 1, result);
headChar = false;
break;
case ',':
result.Append(json[i]);
result.Append("\r\n");
headChar = true;
break;
default:
if (headChar)
{
result.Append(newPadString.ToString());
}
result.Append(json[i]);
headChar = false;
break;
}
}

Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'");
ThrowTerminatingError(NewError());
return -1;
}

/// <summary>
/// Convert the quoted string.
/// </summary>
/// <param name="json"></param>
/// <param name="index"></param>
/// <param name="result"></param>
/// <returns></returns>
private int ConvertQuotedString(string json, int index, StringBuilder result)
{
for (int i = index; i < json.Length; i++)
{
result.Append(json[i]);
if (json[i] == '"')
{
// Ensure that the quote is not escaped by iteratively searching backwards for the backslash.
// Examples:
// "a \" b" --> here second quote is escaped
// "c:\\" --> here second quote is not escaped
//
var j = i;
var escaped = false;
while (j > 0 && json[--j] == '\\')
{
escaped = !escaped;
}

if (!escaped)
{
return i;
}
}
}

Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'");
ThrowTerminatingError(NewError());
return -1;
}

/// <summary>
/// Convert a Json dictionary, which starts with '{'.
/// </summary>
/// <param name="json"></param>
/// <param name="index"></param>
/// <param name="result"></param>
/// <param name="padString"></param>
/// <param name="numberOfSpaces"></param>
/// <returns></returns>
private int ConvertDictionary(string json, int index, StringBuilder result, string padString, int numberOfSpaces)
{
result.Append("\r\n");
StringBuilder newPadString = new StringBuilder();
newPadString.Append(padString);
AddSpaces(numberOfSpaces, newPadString);
AddIndentations(1, newPadString);

bool headChar = true;
bool beforeQuote = true;
int newSpaceCount = 0;
const int spaceCountAfterQuoteMark = 1;

for (int i = index; i < json.Length; i++)
{
switch (json[i])
{
case '{':
result.Append(json[i]);
i = ConvertDictionary(json, i + 1, result, newPadString.ToString(), newSpaceCount);
headChar = false;
break;
case '[':
result.Append(json[i]);
i = ConvertList(json, i + 1, result, newPadString.ToString(), newSpaceCount);
headChar = false;
break;
case '}':
result.Append("\r\n");
result.Append(padString);
AddSpaces(numberOfSpaces, result);
result.Append(json[i]);
return i;
case '"':
if (headChar)
{
result.Append(newPadString.ToString());
}
result.Append(json[i]);
int end = ConvertQuotedString(json, i + 1, result);
if (beforeQuote)
{
newSpaceCount = 0;
}
i = end;
headChar = false;
break;
case ':':
result.Append(json[i]);
AddSpaces(spaceCountAfterQuoteMark, result);
headChar = false;
beforeQuote = false;
break;
case ',':
result.Append(json[i]);
result.Append("\r\n");
headChar = true;
beforeQuote = true;
newSpaceCount = 0;
break;
default:
if (headChar)
{
result.Append(newPadString.ToString());
}
result.Append(json[i]);
if (beforeQuote)
{
newSpaceCount += 1;
}
headChar = false;
break;
}
}

Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'");
ThrowTerminatingError(NewError());
return -1;
}

/// <summary>
/// Add tabs to result
/// </summary>
/// <param name="numberOfTabsToReturn"></param>
/// <param name="result"></param>
private void AddIndentations(int numberOfTabsToReturn, StringBuilder result)
{
int realNumber = numberOfTabsToReturn * 2;
for (int i = 0; i < realNumber; i++)
{
result.Append(' ');
}
}

/// <summary>
/// Add spaces to result
/// </summary>
/// <param name="numberOfSpacesToReturn"></param>
/// <param name="result"></param>
private void AddSpaces(int numberOfSpacesToReturn, StringBuilder result)
{
for (int i = 0; i < numberOfSpacesToReturn; i++)
{
result.Append(' ');
}
}

private ErrorRecord NewError()
{
ErrorRecord errorRecord = new ErrorRecord(
new InvalidOperationException(WebCmdletStrings.JsonStringInBadFormat),
"JsonStringInBadFormat",
ErrorCategory.InvalidOperation,
InputObject);
return errorRecord;
}

#endregion convertOutputToPrettierFormat

/// <summary>
/// Return an alternate representation of the specified object that serializes the same JSON, except
/// that properties that cannot be evaluated are treated as having the value null.
Expand All @@ -401,6 +143,11 @@ private ErrorRecord NewError()
/// <returns>An object suitable for serializing to JSON</returns>
private object ProcessValue(object obj, int depth)
{
if (Stopping)
{
throw new StoppingException();
}

PSObject pso = obj as PSObject;

if (pso != null)
Expand Down Expand Up @@ -432,6 +179,8 @@ private object ProcessValue(object obj, int depth)
else
{
TypeInfo t = obj.GetType().GetTypeInfo();
WriteVerbose(StringUtil.Format(UtilityCommonStrings.ConvertToJsonProcessValueVerboseMessage, t.Name, depth));


if (t.IsPrimitive)
{
Expand Down Expand Up @@ -683,5 +432,10 @@ private object ProcessCustomObject<T>(object o, int depth)
}
return result;
}

/// <summary>
/// Exception used for Stopping.
/// </summary>
private class StoppingException : System.Exception {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,7 @@
<data name="NoZoneIdentifierFileStream" xml:space="preserve">
<value>The file is not blocked: {0}</value>
</data>
<data name="ConvertToJsonProcessValueVerboseMessage" xml:space="preserve">
<value>Processing object of type [{0}] at depth {1}</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,23 @@ Describe 'ConvertTo-Json' -tags "CI" {
$jsonFormat | Should Match '"TestValue2": 78910'
$jsonFormat | Should Match '"TestValue3": 99999'
}

It "StopProcessing should succeed" {
$ps = [PowerShell]::Create()
$null = $ps.AddScript({
$obj = [PSCustomObject]@{P1 = ''; P2 = ''; P3 = ''; P4 = ''; P5 = ''; P6 = ''}
$obj.P1 = $obj.P2 = $obj.P3 = $obj.P4 = $obj.P5 = $obj.P6 = $obj
1..100 | Foreach-Object { $obj } | ConvertTo-Json -Depth 10 -Verbose
# the conversion is expected to take some time, this throw is in case it doesn't
throw "Should not have thrown exception"
})
$null = $ps.BeginInvoke()
# wait for verbose message from ConvertTo-Json to ensure cmdlet is processing
Wait-UntilTrue { $ps.Streams.Verbose.Count -gt 0 }
$null = $ps.BeginStop($null, $null)
# wait a bit to ensure state has changed, not using synchronous Stop() to avoid blocking Pester
Start-Sleep -Milliseconds 100
$ps.InvocationStateInfo.State | Should -BeExactly "Stopped"
$ps.Dispose()
}
}