diff --git a/.azure-pipelines/generation-templates/authentication-module.yml b/.azure-pipelines/generation-templates/authentication-module.yml index 86b7171a52..4dc2d7148d 100644 --- a/.azure-pipelines/generation-templates/authentication-module.yml +++ b/.azure-pipelines/generation-templates/authentication-module.yml @@ -30,6 +30,15 @@ steps: script: | . $(System.DefaultWorkingDirectory)/tools/GenerateAuthenticationModule.ps1 -Test + - ${{ if eq(parameters.Test, true) }}: + - task: PowerShell@2 + displayName: Test Json Utilities + inputs: + pwsh: true + targetType: inline + script: dotnet test + workingDirectory: "$(System.DefaultWorkingDirectory)/tools/Tests/JsonUtilitiesTest" + - ${{ if eq(parameters.Sign, true) }}: - template: ../common-templates/esrp/strongname.yml parameters: diff --git a/autorest.powershell b/autorest.powershell index 1865c6e579..e2dd039108 160000 --- a/autorest.powershell +++ b/autorest.powershell @@ -1 +1 @@ -Subproject commit 1865c6e579269c45a45ad1c6cf6d856b6d27643f +Subproject commit e2dd0391085d76318d6f19001dce7938eb8281e1 diff --git a/src/readme.graph.md b/src/readme.graph.md index 3d0df15c85..3e7b0ef49b 100644 --- a/src/readme.graph.md +++ b/src/readme.graph.md @@ -335,6 +335,11 @@ directive: let dateTimeToJsonRegex = /(\.Json\.JsonString\()(.*)\?(\.ToString\(@"yyyy'-'MM'-'dd'T'HH':'mm':'ss\.fffffffK")/gm $ = $.replace(dateTimeToJsonRegex, '$1System.DateTime.SpecifyKind($2.Value.ToUniversalTime(), System.DateTimeKind.Utc)$3'); + // Enables null valued properties + $ = $.replace(/AddIf\(\s*null\s*!=\s*(this\._\w+)\s*\?\s*\(\s*Microsoft\.Graph\.PowerShell\.Runtime\.Json\.JsonNode\)\s*(.*)\s*:\s*null\s*,\s*"(.*?)"\s*,\s*container\.Add\s*\)/gm, 'container.Add("$3", $1 != null ? (Microsoft.Graph.PowerShell.Runtime.Json.JsonNode) $2 :"defaultnull")') + + $ = $.replace(/AddIf\(\s*null\s*!=\s*\(\(\(\(object\)\s*(this\._\w+)\)\)?.ToString\(\)\)\s*\?\s*\(\s*Microsoft\.Graph\.PowerShell\.Runtime\.Json\.JsonNode\)\s*new\s*Microsoft\.Graph\.PowerShell\.Runtime\.Json\.JsonString\((this\._\w+).ToString\(\)\)\s*:\s*null\s*,\s*"(.*?)"\s*,\s*container\.Add\s*\)/gm, 'container.Add("$3", $1 != null ? (Microsoft.Graph.PowerShell.Runtime.Json.JsonNode) new Microsoft.Graph.PowerShell.Runtime.Json.JsonString($2.ToString()) :"defaultnull")'); + return $; } # Modify generated .dictionary.cs model classes. diff --git a/tools/Custom/JsonExtensions.cs b/tools/Custom/JsonExtensions.cs new file mode 100644 index 0000000000..0c0a69d888 --- /dev/null +++ b/tools/Custom/JsonExtensions.cs @@ -0,0 +1,62 @@ +namespace Microsoft.Graph.PowerShell.JsonUtilities +{ + using Newtonsoft.Json.Linq; + using System.Linq; + + public static class JsonExtensions + { + /// + /// Removes JSON properties that have a value of "defaultnull" and converts properties with values of "null" to actual JSON null values. + /// + /// The JObject to process and clean. + /// + /// A JSON string representation of the cleaned JObject with "defaultnull" properties removed and "null" values converted to JSON null. + /// + /// + /// JObject json = JObject.Parse(@"{""name"": ""John"", ""email"": ""defaultnull"", ""address"": ""null""}"); + /// string cleanedJson = json.RemoveDefaultNullProperties(); + /// Console.WriteLine(cleanedJson); + /// // Output: { "name": "John", "address": null } + /// + public static string RemoveDefaultNullProperties(this JObject jsonObject) + { + try + { + foreach (var property in jsonObject.Properties().ToList()) + { + if (property.Value.Type == JTokenType.Object) + { + RemoveDefaultNullProperties((JObject)property.Value); + } + else if (property.Value.Type == JTokenType.Array) + { + foreach (var item in property.Value) + { + if (item.Type == JTokenType.Object) + { + RemoveDefaultNullProperties((JObject)item); + } + } + } + else if (property.Value.Type == JTokenType.String && property.Value.ToString() == "defaultnull") + { + property.Remove(); + } + else if (property.Value.Type == JTokenType.String && (property.Value.ToString() == "null")) + { + property.Value = JValue.CreateNull(); + } + } + } + catch (System.Exception) + { + return jsonObject.ToString(); // Return the original string if parsing fails + } + return jsonObject.ToString(); + } + public static string ReplaceAndRemoveSlashes(this string body) + { + return body.Replace("/", "").Replace("\\", "").Replace("rn", "").Replace("\"{", "{").Replace("}\"", "}"); + } + } +} diff --git a/tools/Tests/JsonUtilitiesTest/JsonExtensionsTests.cs b/tools/Tests/JsonUtilitiesTest/JsonExtensionsTests.cs new file mode 100644 index 0000000000..ed45257e94 --- /dev/null +++ b/tools/Tests/JsonUtilitiesTest/JsonExtensionsTests.cs @@ -0,0 +1,128 @@ +namespace JsonUtilitiesTest; +using System; +using Newtonsoft.Json.Linq; +using Xunit; +using Microsoft.Graph.PowerShell.JsonUtilities; + +public class JsonExtensionsTests +{ + [Fact] + public void RemoveDefaultNullProperties_ShouldRemoveDefaultNullValues() + { + // Arrange + JObject json = JObject.Parse(@"{ + ""displayname"": ""Tim"", + ""position"": ""defaultnull"", + ""salary"": 2000000, + ""team"": ""defaultnull"" + }"); + + // Act + string cleanedJson = json.RemoveDefaultNullProperties(); + JObject result = JObject.Parse(cleanedJson); + + // Assert + Assert.False(result.ContainsKey("position")); + Assert.False(result.ContainsKey("team")); + Assert.Equal("Tim", result["displayname"]?.ToString()); + Assert.Equal(2000000, result["salary"]?.ToObject()); + } + + [Fact] + public void RemoveDefaultNullProperties_ShouldConvertStringNullToJsonNull() + { + // Arrange + JObject json = JObject.Parse(@"{ + ""displayname"": ""Tim"", + ""position"": ""null"", + ""salary"": 2000000, + ""team"": """" + }"); + + // Act + string cleanedJson = json.RemoveDefaultNullProperties(); + JObject result = JObject.Parse(cleanedJson); + + // Assert + Assert.Null(result["position"]?.Value()); + Assert.Equal("",result["team"]?.ToString()); + Assert.Equal("Tim", result["displayname"]?.ToString()); + Assert.Equal(2000000, result["salary"]?.ToObject()); + } + + [Fact] + public void RemoveDefaultNullProperties_ShouldHandleNestedObjects() + { + // Arrange + JObject json = JObject.Parse(@"{ + ""displayname"": ""Tim"", + ""metadata"": { + ""phone"": ""defaultnull"", + ""location"": ""Nairobi"" + } + }"); + + // Act + string cleanedJson = json.RemoveDefaultNullProperties(); + JObject result = JObject.Parse(cleanedJson); + + // Assert + Assert.False(result["metadata"]?.ToObject()?.ContainsKey("phone")); + Assert.Equal("Nairobi", result["metadata"]?["location"]?.ToString()); + } + + [Fact] + public void RemoveDefaultNullProperties_ShouldHandleEmptyJsonObject() + { + // Arrange + JObject json = JObject.Parse(@"{}"); + + // Act + string cleanedJson = json.RemoveDefaultNullProperties(); + JObject result = JObject.Parse(cleanedJson); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void RemoveDefaultNullProperties_ShouldHandleJsonArrays() + { + // Arrange + JObject json = JObject.Parse(@"{ + ""users"": [ + { ""displayname"": ""Tim"", ""email"": ""defaultnull"" }, + { ""displayname"": ""Mayabi"", ""email"": ""mayabi@example.com"" } + ] + }"); + + // Act + string cleanedJson = json.RemoveDefaultNullProperties(); + JObject result = JObject.Parse(cleanedJson); + + // Assert + Assert.Equal("Tim", result["users"]?[0]?["displayname"]?.ToString()); + Assert.Equal("mayabi@example.com", result["users"]?[1]?["email"]?.ToString()); + } + + [Fact] + public void RemoveDefaultNullProperties_ShouldNotAlterValidData() + { + // Arrange + JObject json = JObject.Parse(@"{ + ""displayname"": ""Tim"", + ""email"": ""mayabi@example.com"", + ""salary"": 2000000 + }"); + + // Act + string cleanedJson = json.RemoveDefaultNullProperties(); + JObject result = JObject.Parse(cleanedJson); + + // Assert + Assert.Equal("Tim", result["displayname"]?.ToString()); + Assert.Equal("mayabi@example.com", result["email"]?.ToString()); + Assert.Equal(2000000, result["salary"]?.ToObject()); + } +} + diff --git a/tools/Tests/JsonUtilitiesTest/JsonUtilitiesTest.csproj b/tools/Tests/JsonUtilitiesTest/JsonUtilitiesTest.csproj new file mode 100644 index 0000000000..2db61912cf --- /dev/null +++ b/tools/Tests/JsonUtilitiesTest/JsonUtilitiesTest.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + false + + + + + + + + + + + + + + + + ../../Custom/JsonExtensions.cs + + + +