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
188 changes: 152 additions & 36 deletions tools/Custom/JsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace NamespacePrefixPlaceholder.PowerShell.JsonUtilities
{
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Linq;

Expand All @@ -20,66 +21,181 @@ public static class JsonExtensions
/// Console.WriteLine(cleanedJson);
/// // Output: { "name": "John", "address": null }
/// </example>

public static string RemoveDefaultNullProperties(this JToken token)
{
try
{
if (token is JObject jsonObject)
ProcessToken(token);

// If the root token is completely empty, return "{}" or "[]"
if (token is JObject obj && !obj.HasValues) return "{}";
if (token is JArray arr && !arr.HasValues) return "[]";

return token.ToString();
}
catch (Exception)
{
return token.ToString(); // Return original JSON if an error occurs
}
}

private static JToken ProcessToken(JToken token)
{
if (token is JObject jsonObject)
{
// Remove properties with "defaultnull" but keep valid ones
var propertiesToRemove = jsonObject.Properties()
.Where(p => p.Value.Type == JTokenType.String && p.Value.ToString().Equals("defaultnull", StringComparison.Ordinal))
.ToList();

foreach (var property in propertiesToRemove)
{
property.Remove();
}

// Recursively process remaining properties
foreach (var property in jsonObject.Properties().ToList())
{
JToken cleanedValue = ProcessToken(property.Value);

// Convert explicit "null" strings to actual null
if (property.Value.Type == JTokenType.String && property.Value.ToString().Equals("null", StringComparison.Ordinal))
{
property.Value = JValue.CreateNull();
}

// Remove the property if it's now empty after processing
if (ShouldRemove(cleanedValue))
{
property.Remove();
}
}

// Remove the object itself if ALL properties are removed (empty object)
return jsonObject.HasValues ? jsonObject : null;
}
else if (token is JArray jsonArray)
{
for (int i = jsonArray.Count - 1; i >= 0; i--)
{
foreach (var property in jsonObject.Properties().ToList())
JToken item = jsonArray[i];

// Process nested objects/arrays inside the array
if (item is JObject || item is JArray)
{
if (property.Value.Type == JTokenType.Object)
JToken cleanedItem = ProcessToken(item);

if (ShouldRemove(cleanedItem))
{
RemoveDefaultNullProperties(property.Value);
jsonArray.RemoveAt(i); // Remove empty or unnecessary items
}
else if (property.Value.Type == JTokenType.Array)
else
{
RemoveDefaultNullProperties(property.Value);
jsonArray[i] = cleanedItem; // Update with cleaned version
}
else if (property.Value.Type == JTokenType.String && property.Value.ToString().Equals("defaultnull",StringComparison.Ordinal))
}
else if (item.Type == JTokenType.String && item.ToString().Equals("null", StringComparison.Ordinal))
{
jsonArray[i] = JValue.CreateNull(); // Convert "null" string to JSON null
}
else if (item.Type == JTokenType.String && item.ToString().Equals("defaultnull", StringComparison.Ordinal))
{
jsonArray.RemoveAt(i); // Remove "defaultnull" entries
}
}

return jsonArray.HasValues ? jsonArray : null;
}

return token;
}

private static bool ShouldRemove(JToken token)
{
return token == null ||
(token.Type == JTokenType.Object && !token.HasValues) || // Remove empty objects
(token.Type == JTokenType.Array && !token.HasValues); // Remove empty arrays
}


public static string ReplaceAndRemoveSlashes(this string body)
{
try
{
// Parse the JSON using Newtonsoft.Json
JToken jsonToken = JToken.Parse(body);
if (jsonToken == null) return body; // If parsing fails, return original body

// Recursively process JSON to remove escape sequences
ProcessBody(jsonToken);

// Return cleaned JSON string
return JsonConvert.SerializeObject(jsonToken, Formatting.None);
}
catch (Newtonsoft.Json.JsonException)
{
// If it's not valid JSON, apply normal string replacements
return body.Replace("\\", "").Replace("rn", "").Replace("\"{", "{").Replace("}\"", "}");
}
}

private static void ProcessBody(JToken token)
{
if (token is JObject jsonObject)
{
foreach (var property in jsonObject.Properties().ToList())
{
var value = property.Value;

// If the value is a string, attempt to parse it as JSON to remove escaping
if (value.Type == JTokenType.String)
{
string stringValue = value.ToString();
try
{
property.Remove();
JToken parsedValue = JToken.Parse(stringValue);
property.Value = parsedValue; // Replace with unescaped JSON object
ProcessBody(parsedValue); // Recursively process
}
else if (property.Value.Type == JTokenType.String && property.Value.ToString().Equals("null",StringComparison.Ordinal))
catch (Newtonsoft.Json.JsonException)
{
property.Value = JValue.CreateNull();
// If parsing fails, leave the value as is
}
}
else if (value is JObject || value is JArray)
{
ProcessBody(value); // Recursively process nested objects/arrays
}
}
else if (token is JArray jsonArray)
}
else if (token is JArray jsonArray)
{
for (int i = 0; i < jsonArray.Count; i++)
{
// Process each item in the JArray
for (int i = jsonArray.Count - 1; i >= 0; i--)
{
var item = jsonArray[i];
var value = jsonArray[i];

if (item.Type == JTokenType.Object)
{
RemoveDefaultNullProperties(item);
}
else if (item.Type == JTokenType.String && item.ToString().Equals("defaultnull",StringComparison.Ordinal))
// If the value is a string, attempt to parse it as JSON to remove escaping
if (value.Type == JTokenType.String)
{
string stringValue = value.ToString();
try
{
jsonArray.RemoveAt(i); // Remove the "defaultnull" string from the array
JToken parsedValue = JToken.Parse(stringValue);
jsonArray[i] = parsedValue; // Replace with unescaped JSON object
ProcessBody(parsedValue); // Recursively process
}
else if (item.Type == JTokenType.String && item.ToString().Equals("null",StringComparison.Ordinal))
catch (Newtonsoft.Json.JsonException)
{
jsonArray[i] = JValue.CreateNull(); // Convert "null" string to actual null
// If parsing fails, leave the value as is
}
}
else if (value is JObject || value is JArray)
{
ProcessBody(value); // Recursively process nested objects/arrays
}
}
}
catch (System.Exception ex)
{
Console.WriteLine($"Error cleaning JSON: {ex.Message}");
return token.ToString(); // Return the original JSON if any error occurs
}

return token.ToString();
}

public static string ReplaceAndRemoveSlashes(this string body)
{
return body.Replace("\\", "").Replace("rn", "").Replace("\"{", "{").Replace("}\"", "}");
}
}
}
}
43 changes: 41 additions & 2 deletions tools/Tests/JsonUtilitiesTest/JsonExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,22 @@ public void RemoveDefaultNullProperties_ShouldHandleNestedObjects()
// Arrange
JObject json = JObject.Parse(@"{
""displayname"": ""Tim"",
""professions"": {
},
""jobProfile"": {
""dept"": ""ICT"",
""manager"": false,
""supervisor"" : ""defaultnull""
},
""metadata"": {
""phone"": ""defaultnull"",
""location"": ""Nairobi""
""location"": ""null"",
""address"": {
""city"": ""Nairobi"",
""street"": ""defaultnull""
},
""station"": {
}
}
}");

Expand All @@ -69,7 +82,12 @@ public void RemoveDefaultNullProperties_ShouldHandleNestedObjects()

// Assert
Assert.False(result["metadata"]?.ToObject<JObject>()?.ContainsKey("phone"));
Assert.Equal("Nairobi", result["metadata"]?["location"]?.ToString());
Assert.Equal("ICT", result["jobProfile"]?["dept"]?.ToString());
Assert.Equal("Nairobi", result["metadata"]?["address"]?["city"]?.ToString());
Assert.Null(result["metadata"]?["location"]?.Value<string>());
// Check if emptynested object is removed
Assert.False(result["metadata"]?.ToObject<JObject>()?.ContainsKey("station "));
Assert.False(result?.ToObject<JObject>()?.ContainsKey("professions"));
}

[Fact]
Expand Down Expand Up @@ -206,6 +224,27 @@ private string NormalizeJson(string json)
});
}

[Fact]
public void RemoveDefaultNullProperties_ShouldRemoveDefaultNullValuesInJsonObjectWithBothDeeplyNestedObjectsAndArrays(){
// Arrange
JObject json = JObject.Parse(@"{
""body"":{
""users"": [
{ ""displayname"": ""Tim"", ""email"": ""defaultnull"", ""metadata"": { ""phone"": ""254714390915"" } }
]
},
""users"": [
{ ""displayname"": ""Tim"", ""email"": ""defaultnull"", ""metadata"": { ""phone"": ""254714390915"" } }]}");

// Act
string cleanedJson = json.RemoveDefaultNullProperties();
JObject result = JObject.Parse(cleanedJson);

// Assert
Assert.False(result["users"][0]?.ToObject<JObject>().ContainsKey("email"));
Assert.True(result["users"][0]?["metadata"]?.ToObject<JObject>().ContainsKey("phone"));
Assert.False(result["body"]?["users"][0]?.ToObject<JObject>().ContainsKey("email"));
Assert.True(result["body"]?["users"][0]?["metadata"]?.ToObject<JObject>().ContainsKey("phone"));
}
}

Loading