using System.Text.Json.Serialization;
TestPayload payload = new(1, "test", true);
ApiResponse<TestPayload> response1 = new() { StatusCode = "OK", Message = "Retrieved something", Data = payload, DataPropName = "user" };
JsonSerializerOptions options = new() { WriteIndented = true };
string json = JsonSerializer.Serialize(response1, options);
Console.WriteLine("JSON:");
ApiResponse<TestPayload>? response2 = JsonSerializer.Deserialize<ApiResponse<TestPayload>>(json, options);
Console.WriteLine("Failed to deserialize JSON to ApiResponse<TestPayload>.");
else if (response2.Data is null)
Console.WriteLine("Deserialization of variable-name property 'Data' failed.");
response2.StatusCode != response1.StatusCode ||
response2.Message != response1.Message ||
response2.Data != response1.Data ||
response2.DataPropName != response1.DataPropName
Console.WriteLine("Deserialization resulted in different data.");
Console.WriteLine("Round-trip serialization/deserialization completed.");
public record TestPayload(int ID, string Name, bool Active);
[JsonConverter(typeof(ApiResponseConverterFactory))]
public class ApiResponse<T>
public string StatusCode { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public T? Data { get; set; }
public string? DataPropName { get; set; }
public class ApiResponseConverterFactory : JsonConverterFactory
public override bool CanConvert(Type typeToConvert)
return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(ApiResponse<>);
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
Type[] typeArguments = typeToConvert.GetGenericArguments();
Type payloadType = typeArguments[0];
JsonConverter converter = (JsonConverter?)Activator.CreateInstance
typeof(ApiResponseConverter<>).MakeGenericType([payloadType]),
BindingFlags.Instance | BindingFlags.Public,
) ?? throw new Exception();
private class ApiResponseConverter<T> : JsonConverter<ApiResponse<T>>
private readonly JsonConverter<T> _valueConverter;
private readonly Type _valType;
public ApiResponseConverter(JsonSerializerOptions options)
_valueConverter = (JsonConverter<T>)options.GetConverter(typeof(T));
public override ApiResponse<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
ApiResponse<T> result = new();
if (reader.TokenType == JsonTokenType.EndObject)
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
string? propName = reader.GetString();
if (string.Compare(propName, "StatusCode", true) == 0)
string? value = reader.GetString();
result.StatusCode = value ?? throw new JsonException();
else if (string.Compare(propName, "Message", true) == 0)
string? value = reader.GetString();
result.Message = value ?? throw new JsonException();
else if (_valueConverter.Read(ref reader, _valType, options) is T data)
result.DataPropName = propName;
throw new JsonException();
throw new JsonException("Unexpected end of input.");
public override void Write(Utf8JsonWriter writer, ApiResponse<T> value, JsonSerializerOptions options)
writer.WriteStartObject();
writer.WriteString("StatusCode", value.StatusCode);
writer.WriteString("Message", value.Message);
string propName = value.DataPropName ?? "Data";
propName = options.PropertyNamingPolicy?.ConvertName(propName) ?? propName;
writer.WritePropertyName(propName);
_valueConverter.Write(writer, value.Data, options);