Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save surfmuggle/e04d4699a3c6c305f4af721d3db0e73d to your computer and use it in GitHub Desktop.
Save surfmuggle/e04d4699a3c6c305f4af721d3db0e73d to your computer and use it in GitHub Desktop.
C# JsonSerializer.Deserialize fails if property has string value "null" despite JsonIgnoreCondition.WhenWritingNull
void Main()
{
// Linqpad program to test the custom serializer to handle strings with value null: string foo = "\"null\"";
// based on this answer https://stackoverflow.com/a/74857003/819887
try
{
//TestClass.Test();
foreach (var jsonWithConfig in GetJson())
{
jsonWithConfig.Dump("jsonWithConfig");
var options = new JsonSerializerOptions
{
Converters = { new NullTextValueForNullObjectConverter<Config>() },
// Other options as required
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true
};
var webResponseWithConfig = JsonSerializer.Deserialize<WebResponse>(jsonWithConfig, options);
webResponseWithConfig.Dump("deserialized to webResponseWithConfig");
var json2 = JsonSerializer.Serialize(webResponseWithConfig , options);
json2.Dump("serialized to webResponseWithConfig");
}
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
// You can define other methods, fields, classes and namespaces here
static IEnumerable<string> GetJson() => new []{
"""
{"config":{"c1":"All","c2":"is peachy"},"message":"We found a config object"}
""",
"""
{"config":"null","message":"Config is \"null\""}
"""
};
public class WebResponse
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonConverter(typeof(NullTextValueForNullObjectConverter<Config>))]
public Config Config { get; set; }
public string Message { get; set; }
}
public class Config
{
public string C1 { get; set; }
public string C2 { get; set; }
}
public sealed class NullTextValueForNullObjectConverter<T> : DefaultConverterFactory<T> where T : class
{
const string NullValue = "null";
protected override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions)
{
if (reader.TokenType == JsonTokenType.String)
{
var s = reader.GetString();
if (string.Equals(s, NullValue, StringComparison.OrdinalIgnoreCase)) // Or use StringComparison.Ordinal if you are sure the text "null" will always be lowercase
return null;
}
return (T)JsonSerializer.Deserialize(ref reader, typeToConvert);
}
}
public abstract class DefaultConverterFactory<T> : JsonConverterFactory
{
class DefaultConverter : JsonConverter<T>
{
readonly JsonSerializerOptions modifiedOptions;
readonly DefaultConverterFactory<T> factory;
public DefaultConverter(JsonSerializerOptions options, DefaultConverterFactory<T> factory)
{
this.factory = factory;
this.modifiedOptions = options.CopyAndRemoveConverter(factory.GetType());
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => factory.Write(writer, value, modifiedOptions);
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => factory.Read(ref reader, typeToConvert, modifiedOptions);
}
protected virtual T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions)
=> (T)JsonSerializer.Deserialize(ref reader, typeToConvert, modifiedOptions);
protected virtual void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions)
=> JsonSerializer.Serialize(writer, value, modifiedOptions);
public override bool CanConvert(Type typeToConvert) => typeof(T) == typeToConvert;
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new DefaultConverter(options, this);
}
public static class JsonSerializerExtensions
{
public static JsonSerializerOptions CopyAndRemoveConverter(this JsonSerializerOptions options, Type converterType)
{
var copy = new JsonSerializerOptions(options);
for (var i = copy.Converters.Count - 1; i >= 0; i--)
if (copy.Converters[i].GetType() == converterType)
copy.Converters.RemoveAt(i);
return copy;
}
}
@surfmuggle
Copy link
Author

surfmuggle commented Dec 20, 2022

To be able to serialize null value if a json property has a string "null" instead of null you can use a custom converter.

custom JsonConverter that checks for a "null" text value and returns null if present.

The code above is a linqpad program that show how to create and use a custom converter

Linqpad program

@surfmuggle
Copy link
Author

It seems that the deserialization omits the Config-object

  • expected: {config: {...}, ...} value of property config is a not empty object {...}
  • actual: {config: {}, ...} value of config property is an empty object {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment