From an external webservice i receive either
// jsonWithConfig
// property config is an object {}
{"config":{"c1":"All","c2":"is peachy"},"message":"We found a config object"}
// jsonNoConfig
// property config is string with the value null
{"config":"null","message":"Config is null"}
I want to deserialize the json into these types
public class WebResponse
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Config Config { get; set; }
public string Message { get; set; }
// i also tried dynamic but
// a) this is not what i want
// b) it resulted in RuntimeBinderException
// public dynamic Config { get; set; }
}
public class Config
{
public string C1 { get; set; }
public string C2 { get; set; }
}
What have i tried?
CodePudding user response:
As explained in comments by MySkullCaveIsADarkPlace, your problem is that the JSON value "null"
"config":"null"
Is not null. It is a non-null string value containing the characters null
. A null value looks like:
"config":null // Notice there are no quotes around the text
For confirmation, see the original JSON proposal.
If you cannot fix the JSON to represent null values properly, you will need to write a custom JsonConverter that checks for a "null"
text value and returns null
if present. If not present, the converter should proceed with default deserialization.
The question How to use default serialization in a custom System.Text.Json JsonConverter? has this answer which provides a DefaultConverterFactory<T>
. Grab it and subclass it as follows:
NullTextValueForNullObjectConverter
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();
// Or use StringComparison.Ordinal if you are sure the
// text "null" will always be lowercase
if (string.Equals(s, NullValue, StringComparison.OrdinalIgnoreCase))
return null;
}
return (T)JsonSerializer.Deserialize(ref reader, typeToConvert);
}
}
DefaultConverterFactory
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);
}
JsonSerializerExtensions
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;
}
}
Then either add the converter to JsonSerializerOptions.Converters
as follows:
var options = new JsonSerializerOptions
{
Converters = { new NullTextValueForNullObjectConverter<Config>() },
// Other options as required
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true
};
var webResponseWithConfig = JsonSerializer.Deserialize<WebResponse>(jsonWithConfig, options);
Or apply to the Config
property directly as follows:
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonConverter(typeof(NullTextValueForNullObjectConverter<Config>))]
public Config Config { get; set; }
Note that there does not appear to be a way currently to generate a default serialization of you apply the converter directly to the Config
type. As such, I don't recommend doing it.
Demo fiddle here.
CodePudding user response:
If you cannot fix the JSON source then in this particular case i would recommend to replace "null"
with null
using a c# string replace function
json = json.Replace("\"null\"","null");