Skip to content

Allows serialization of null valued properties. #3126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 15, 2025
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 @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions src/readme.graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
62 changes: 62 additions & 0 deletions tools/Custom/JsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
namespace Microsoft.Graph.PowerShell.JsonUtilities
{
using Newtonsoft.Json.Linq;
using System.Linq;

public static class JsonExtensions
{
/// <summary>
/// Removes JSON properties that have a value of "defaultnull" and converts properties with values of "null" to actual JSON null values.
/// </summary>
/// <param name="jsonObject">The JObject to process and clean.</param>
/// <returns>
/// A JSON string representation of the cleaned JObject with "defaultnull" properties removed and "null" values converted to JSON null.
/// </returns>
/// <example>
/// JObject json = JObject.Parse(@"{""name"": ""John"", ""email"": ""defaultnull"", ""address"": ""null""}");
/// string cleanedJson = json.RemoveDefaultNullProperties();
/// Console.WriteLine(cleanedJson);
/// // Output: { "name": "John", "address": null }
/// </example>
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("}\"", "}");
}
}
}
128 changes: 128 additions & 0 deletions tools/Tests/JsonUtilitiesTest/JsonExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -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<int>());
}

[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<string>());
Assert.Equal("",result["team"]?.ToString());
Assert.Equal("Tim", result["displayname"]?.ToString());
Assert.Equal(2000000, result["salary"]?.ToObject<int>());
}

[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<JObject>()?.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<int>());
}
}

27 changes: 27 additions & 0 deletions tools/Tests/JsonUtilitiesTest/JsonUtilitiesTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<Compile Include="../../Custom/JsonExtensions.cs">
<Link>../../Custom/JsonExtensions.cs</Link>
</Compile>
</ItemGroup>

</Project>
Loading