Skip to content

Commit e3fe823

Browse files
authored
Merge pull request #3126 from microsoftgraph/enable-null-valued-properties
Allows serialization of null valued properties.
2 parents b658b01 + 75d4b27 commit e3fe823

File tree

6 files changed

+232
-1
lines changed

6 files changed

+232
-1
lines changed

.azure-pipelines/generation-templates/authentication-module.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ steps:
3030
script: |
3131
. $(System.DefaultWorkingDirectory)/tools/GenerateAuthenticationModule.ps1 -Test
3232
33+
- ${{ if eq(parameters.Test, true) }}:
34+
- task: PowerShell@2
35+
displayName: Test Json Utilities
36+
inputs:
37+
pwsh: true
38+
targetType: inline
39+
script: dotnet test
40+
workingDirectory: "$(System.DefaultWorkingDirectory)/tools/Tests/JsonUtilitiesTest"
41+
3342
- ${{ if eq(parameters.Sign, true) }}:
3443
- template: ../common-templates/esrp/strongname.yml
3544
parameters:

src/readme.graph.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,11 @@ directive:
335335
let dateTimeToJsonRegex = /(\.Json\.JsonString\()(.*)\?(\.ToString\(@"yyyy'-'MM'-'dd'T'HH':'mm':'ss\.fffffffK")/gm
336336
$ = $.replace(dateTimeToJsonRegex, '$1System.DateTime.SpecifyKind($2.Value.ToUniversalTime(), System.DateTimeKind.Utc)$3');
337337
338+
// Enables null valued properties
339+
$ = $.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")')
340+
341+
$ = $.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")');
342+
338343
return $;
339344
}
340345
# Modify generated .dictionary.cs model classes.

tools/Custom/JsonExtensions.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace Microsoft.Graph.PowerShell.JsonUtilities
2+
{
3+
using Newtonsoft.Json.Linq;
4+
using System.Linq;
5+
6+
public static class JsonExtensions
7+
{
8+
/// <summary>
9+
/// Removes JSON properties that have a value of "defaultnull" and converts properties with values of "null" to actual JSON null values.
10+
/// </summary>
11+
/// <param name="jsonObject">The JObject to process and clean.</param>
12+
/// <returns>
13+
/// A JSON string representation of the cleaned JObject with "defaultnull" properties removed and "null" values converted to JSON null.
14+
/// </returns>
15+
/// <example>
16+
/// JObject json = JObject.Parse(@"{""name"": ""John"", ""email"": ""defaultnull"", ""address"": ""null""}");
17+
/// string cleanedJson = json.RemoveDefaultNullProperties();
18+
/// Console.WriteLine(cleanedJson);
19+
/// // Output: { "name": "John", "address": null }
20+
/// </example>
21+
public static string RemoveDefaultNullProperties(this JObject jsonObject)
22+
{
23+
try
24+
{
25+
foreach (var property in jsonObject.Properties().ToList())
26+
{
27+
if (property.Value.Type == JTokenType.Object)
28+
{
29+
RemoveDefaultNullProperties((JObject)property.Value);
30+
}
31+
else if (property.Value.Type == JTokenType.Array)
32+
{
33+
foreach (var item in property.Value)
34+
{
35+
if (item.Type == JTokenType.Object)
36+
{
37+
RemoveDefaultNullProperties((JObject)item);
38+
}
39+
}
40+
}
41+
else if (property.Value.Type == JTokenType.String && property.Value.ToString() == "defaultnull")
42+
{
43+
property.Remove();
44+
}
45+
else if (property.Value.Type == JTokenType.String && (property.Value.ToString() == "null"))
46+
{
47+
property.Value = JValue.CreateNull();
48+
}
49+
}
50+
}
51+
catch (System.Exception)
52+
{
53+
return jsonObject.ToString(); // Return the original string if parsing fails
54+
}
55+
return jsonObject.ToString();
56+
}
57+
public static string ReplaceAndRemoveSlashes(this string body)
58+
{
59+
return body.Replace("/", "").Replace("\\", "").Replace("rn", "").Replace("\"{", "{").Replace("}\"", "}");
60+
}
61+
}
62+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
namespace JsonUtilitiesTest;
2+
using System;
3+
using Newtonsoft.Json.Linq;
4+
using Xunit;
5+
using Microsoft.Graph.PowerShell.JsonUtilities;
6+
7+
public class JsonExtensionsTests
8+
{
9+
[Fact]
10+
public void RemoveDefaultNullProperties_ShouldRemoveDefaultNullValues()
11+
{
12+
// Arrange
13+
JObject json = JObject.Parse(@"{
14+
""displayname"": ""Tim"",
15+
""position"": ""defaultnull"",
16+
""salary"": 2000000,
17+
""team"": ""defaultnull""
18+
}");
19+
20+
// Act
21+
string cleanedJson = json.RemoveDefaultNullProperties();
22+
JObject result = JObject.Parse(cleanedJson);
23+
24+
// Assert
25+
Assert.False(result.ContainsKey("position"));
26+
Assert.False(result.ContainsKey("team"));
27+
Assert.Equal("Tim", result["displayname"]?.ToString());
28+
Assert.Equal(2000000, result["salary"]?.ToObject<int>());
29+
}
30+
31+
[Fact]
32+
public void RemoveDefaultNullProperties_ShouldConvertStringNullToJsonNull()
33+
{
34+
// Arrange
35+
JObject json = JObject.Parse(@"{
36+
""displayname"": ""Tim"",
37+
""position"": ""null"",
38+
""salary"": 2000000,
39+
""team"": """"
40+
}");
41+
42+
// Act
43+
string cleanedJson = json.RemoveDefaultNullProperties();
44+
JObject result = JObject.Parse(cleanedJson);
45+
46+
// Assert
47+
Assert.Null(result["position"]?.Value<string>());
48+
Assert.Equal("",result["team"]?.ToString());
49+
Assert.Equal("Tim", result["displayname"]?.ToString());
50+
Assert.Equal(2000000, result["salary"]?.ToObject<int>());
51+
}
52+
53+
[Fact]
54+
public void RemoveDefaultNullProperties_ShouldHandleNestedObjects()
55+
{
56+
// Arrange
57+
JObject json = JObject.Parse(@"{
58+
""displayname"": ""Tim"",
59+
""metadata"": {
60+
""phone"": ""defaultnull"",
61+
""location"": ""Nairobi""
62+
}
63+
}");
64+
65+
// Act
66+
string cleanedJson = json.RemoveDefaultNullProperties();
67+
JObject result = JObject.Parse(cleanedJson);
68+
69+
// Assert
70+
Assert.False(result["metadata"]?.ToObject<JObject>()?.ContainsKey("phone"));
71+
Assert.Equal("Nairobi", result["metadata"]?["location"]?.ToString());
72+
}
73+
74+
[Fact]
75+
public void RemoveDefaultNullProperties_ShouldHandleEmptyJsonObject()
76+
{
77+
// Arrange
78+
JObject json = JObject.Parse(@"{}");
79+
80+
// Act
81+
string cleanedJson = json.RemoveDefaultNullProperties();
82+
JObject result = JObject.Parse(cleanedJson);
83+
84+
// Assert
85+
Assert.Empty(result);
86+
}
87+
88+
[Fact]
89+
public void RemoveDefaultNullProperties_ShouldHandleJsonArrays()
90+
{
91+
// Arrange
92+
JObject json = JObject.Parse(@"{
93+
""users"": [
94+
{ ""displayname"": ""Tim"", ""email"": ""defaultnull"" },
95+
{ ""displayname"": ""Mayabi"", ""email"": ""mayabi@example.com"" }
96+
]
97+
}");
98+
99+
// Act
100+
string cleanedJson = json.RemoveDefaultNullProperties();
101+
JObject result = JObject.Parse(cleanedJson);
102+
103+
// Assert
104+
Assert.Equal("Tim", result["users"]?[0]?["displayname"]?.ToString());
105+
Assert.Equal("mayabi@example.com", result["users"]?[1]?["email"]?.ToString());
106+
}
107+
108+
[Fact]
109+
public void RemoveDefaultNullProperties_ShouldNotAlterValidData()
110+
{
111+
// Arrange
112+
JObject json = JObject.Parse(@"{
113+
""displayname"": ""Tim"",
114+
""email"": ""mayabi@example.com"",
115+
""salary"": 2000000
116+
}");
117+
118+
// Act
119+
string cleanedJson = json.RemoveDefaultNullProperties();
120+
JObject result = JObject.Parse(cleanedJson);
121+
122+
// Assert
123+
Assert.Equal("Tim", result["displayname"]?.ToString());
124+
Assert.Equal("mayabi@example.com", result["email"]?.ToString());
125+
Assert.Equal(2000000, result["salary"]?.ToObject<int>());
126+
}
127+
}
128+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" Version="6.0.2" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
13+
<PackageReference Include="xunit" Version="2.9.2" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<Using Include="Xunit" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<Compile Include="../../Custom/JsonExtensions.cs">
23+
<Link>../../Custom/JsonExtensions.cs</Link>
24+
</Compile>
25+
</ItemGroup>
26+
27+
</Project>

0 commit comments

Comments
 (0)