Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
seungyongshim committed Apr 8, 2024
1 parent b13c0c9 commit fbb6f20
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 33 deletions.
23 changes: 23 additions & 0 deletions src/SystemTextJsonNullTests/SystemTextJsonNullTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>

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

</Project>
275 changes: 275 additions & 0 deletions src/SystemTextJsonNullTests/UnitTest1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Newtonsoft.Json.Linq;

namespace SystemTextJsonNullTests;

public class UnitTest1
{




public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
WriteIndented = false,
PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate,
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { InterceptNullSetter, }
}
};

static void InterceptNullSetter(JsonTypeInfo typeInfo)
{
static bool IsRequiredMember(JsonPropertyInfo propertyInfo) =>
propertyInfo.AttributeProvider?.GetCustomAttributes(typeof(System.Runtime.CompilerServices.RequiredMemberAttribute), true).Any() ?? false;


foreach (var (propertyInfo, setProperty) in from propertyInfo in typeInfo.Properties
let setProperty = propertyInfo.Set
where setProperty is not null
select (propertyInfo, setProperty))
{
propertyInfo.Set = (obj, value) =>
{
if (value is null)
{
if (IsRequiredMember(propertyInfo))
{
throw new JsonException($"Null value not allowed for '{propertyInfo.Name}'");
}
NullabilityInfoContext context = new();
var nullabilityInfo = propertyInfo.AttributeProvider switch
{
FieldInfo fieldInfo => context.Create(fieldInfo),
PropertyInfo propertyInfo => context.Create(propertyInfo),
_ => null
};
if (nullabilityInfo?.WriteState is NullabilityState.Nullable)
{
setProperty(obj, value);
}
}
else
{
setProperty(obj, value);
}
};
}
}

[Fact]
public void RequireValueNull()
{
var json = """
{
"RequireValue": null
}
""";

var exception = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions));

Assert.Equal("Null value not allowed for 'requireValue'", exception.Message);
}

[Fact]
public void RequireValueNull2()
{
var json = "{}";

var exception = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions));

Assert.Equal("JSON deserialization for type 'SystemTextJsonNullTests.Dto' was missing required properties, including the following: requireValue", exception.Message);
}

[Fact]
public void NotNullDefaultValueNull()
{
var json = """
{
"RequireValue": "",
"NotNullDefaultValue": null
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal(sut.NotNullDefaultValue, "");
}

[Fact]
public void NotNullDefaultValueNull2()
{
var json = """
{
"RequireValue": ""
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal(sut.NotNullDefaultValue, "");
}

[Fact]
public void NullableDefaultValueExpectDefault()
{
var json = """
{
"RequireValue": ""
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal("", sut.NullableDefaultValue);
}

[Fact]
public void NullableDefaultValueExpectNull()
{
var json = """
{
"RequireValue": "",
"NullableDefaultValue": null
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal(sut.NullableDefaultValue, null);
}

[Fact]
public void NullableDefaultValueExpectAAA()
{
var json = """
{
"RequireValue": "",
"NullableDefaultValue": "AAA"
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal("", sut.RequireValue);
Assert.Equal("AAA", sut.NullableDefaultValue);
}

[Fact]
public void NullableValueExpectDefault()
{
var json = """
{
"RequireValue": ""
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal(sut.NullableValue, null);
}

[Fact]
public void NullableValueExpectNull()
{
var json = """
{
"RequireValue": "",
"NullableValue": null
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal(sut.NullableValue, null);
}

[Fact]
public void NullableValueExpectCCC()
{
var json = """
{
"RequireValue": "",
"NullableValue": "CCC"
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal("", sut.RequireValue);
Assert.Equal(sut.NullableValue, "CCC");
}


[Fact]
public void StringValuesExpectDefault()
{
var json = """
{
"RequireValue": ""
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal(sut.StringValues, []);
}

[Fact]
public void StringValuesExpectNull()
{
var json = """
{
"RequireValue": "",
"StringValues": null
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal(sut.RequireValue, "");
Assert.Equal(sut.StringValues, []);
}

[Fact]
public void StringValuesExpectCCC()
{
var json = """
{
"RequireValue": "",
"StringValues": ["CCC"]
}
""";

var sut = JsonSerializer.Deserialize<Dto>(json, JsonSerializerOptions);

Assert.Equal("", sut.RequireValue);
Assert.Equal(sut.StringValues, ["CCC"]);
}
}


public record Dto
{
public required string RequireValue { get; init; }
public string NotNullDefaultValue { get; init; } = "";
public string? NullableDefaultValue { get; init; } = "";
public string? NullableValue { get; init; }
public IEnumerable<string> StringValues { get; init; } = [];
}
1 change: 0 additions & 1 deletion src/WebApplicationMinimalApi8/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@
root.MapPost("/echo", async (HttpContext ctx, JsonDocument json) =>
{
using var httpclient = ctx.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient("echo");
using var res = await httpclient.PostAsJsonAsync("post", json);
return await res.Content.ReadAsStringAsync();
Expand Down
8 changes: 7 additions & 1 deletion src/best-practice-asp-core-8.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplicationMinimalApi8", "WebApplicationMinimalApi8\WebApplicationMinimalApi8.csproj", "{D82EF501-FD42-4E1F-97B6-B84C50913C2D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApplicationMinimalApi8", "WebApplicationMinimalApi8\WebApplicationMinimalApi8.csproj", "{D82EF501-FD42-4E1F-97B6-B84C50913C2D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemTextJsonNullTests", "SystemTextJsonNullTests\SystemTextJsonNullTests.csproj", "{425D5952-8632-4FFD-83A9-55833F06A76D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -21,6 +23,10 @@ Global
{D82EF501-FD42-4E1F-97B6-B84C50913C2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D82EF501-FD42-4E1F-97B6-B84C50913C2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D82EF501-FD42-4E1F-97B6-B84C50913C2D}.Release|Any CPU.Build.0 = Release|Any CPU
{425D5952-8632-4FFD-83A9-55833F06A76D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{425D5952-8632-4FFD-83A9-55833F06A76D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{425D5952-8632-4FFD-83A9-55833F06A76D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{425D5952-8632-4FFD-83A9-55833F06A76D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
31 changes: 0 additions & 31 deletions src/src.sln

This file was deleted.

0 comments on commit fbb6f20

Please sign in to comment.