Ⅰ. はじめに
タイトルの通り「ASP.NETでnullが明示的に指定されたかを取得する方法」です。
Ⅱ. 前提条件
- .NET 7.0以上
Ⅲ. 手順
1. プログラムを書く
OptionalConverter.cs
// https://stackoverflow.com/questions/71024060 using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; public readonly struct Optional<T> { public Optional(T? value) { this.HasValue = true; this.Value = value; } public bool HasValue { get; } public T? Value { get; } public static implicit operator Optional<T>(T value) => new Optional<T>(value); public override string ToString() => this.HasValue ? (this.Value?.ToString() ?? "null") : "unspecified"; } public class OptionalConverter : JsonConverterFactory { public override bool CanConvert(Type typeToConvert) { if (!typeToConvert.IsGenericType) { return false; } if (typeToConvert.GetGenericTypeDefinition() != typeof(Optional<>)) { return false; } return true; } public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { Type valueType = typeToConvert.GetGenericArguments()[0]; return (JsonConverter)Activator.CreateInstance( type: typeof(OptionalConverterInner<>).MakeGenericType(new Type[] { valueType }), bindingAttr: BindingFlags.Instance | BindingFlags.Public, binder: null, args: null, culture: null )!; } private class OptionalConverterInner<T> : JsonConverter<Optional<T>> { public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { T? value = JsonSerializer.Deserialize<T>(ref reader, options); return new Optional<T>(value); } public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value.Value, options); } }
Program.cs
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers() .AddJsonOptions(o => o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault) .AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new OptionalConverter()));
Controllers/ApiController
public class TestRequest { [JsonPropertyName("value")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional<string?> Value { get; set; } } [HttpPost("test")] public IActionResult Test(TestRequest request) { Console.WriteLine($"HasValue:{request.Value.HasValue}, Value:{request.Value.Value}"); return Ok(); }
実行結果
リクエスト内容 | 出力 |
{ "value": "abc" } | HasValue:True, Value:abc |
{ "value": null } | HasValue:True, Value: |
{ } | HasValue:False, Value: |
Swagger対応について
- MapTypeで明示的に指定する事で対応できる
- Optional<T>に対しては面倒な方法しかない
※Tはプリミティブでは無い型。自作クラスなど。
builder.Services.AddSwaggerGen(x => { x.MapType<Optional<string>>(() => new OpenApiSchema { Type = "string" }); });
Before | |
After |