[英]C# JsonSerializer.Deserialize fails if property has null value despite JsonIgnoreCondition.WhenWritingNull
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我想将 json 反序列化为这些类型
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; }
}
From How to ignore properties with System.Text.Json i started with JsonSerializer.Deserialize from System.Text.Json
从How to ignore properties with System.Text.Json我从
System.Text.Json
开始使用JsonSerializer.Deserialize
var options = new JsonSerializerOptions{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true
};
string jsonWithConfig =
{"config":{"c1":"All","c2":"is peachy"},"message":"We found a Config"}
WebResponse webResponseWithConfig =
JsonSerializer.Deserialize<WebResponse>(jsonWithConfig, options);
This works for jsonWithConfig
which is no surprise since the json can be deserilized to the type WebResponse
.这适用于
jsonWithConfig
这并不奇怪,因为 json 可以反序列化为类型WebResponse
。
I hoped that using JsonSerializerOptions.DefaultIgnoreCondition would work for jsonNoConfig = {"config":"null","message":"Config is null"}
.我希望使用JsonSerializerOptions.DefaultIgnoreCondition对
jsonNoConfig = {"config":"null","message":"Config is null"}
。 But deserialization of jsonNoConfig
fails with DeserializeUnableToConvertValue
但是
jsonNoConfig
的反序列化因DeserializeUnableToConvertValue
而失败
string jsonNoConfig =
{"config":"null","message":"Config is null"}
WebResponse webResponseNoConfig =
JsonSerializer.Deserialize<WebResponse>(jsonNoConfig, options);
jsonNoConfig
?jsonNoConfig
?MySkullCaveIsADarkPlace pointed out that config
should have the value null
and not "null"
. MySkullCaveIsADarkPlace指出
config
的值应该是null
而不是"null"
。 After changing this the code above works as expected.更改此代码后,上面的代码将按预期工作。
But is there a way to handle null with quotation marks like {"config":"null", ...}
as well?但是有没有一种方法可以用
{"config":"null", ...}
这样的引号来处理 null 呢?
The stack trace inside linqpad shows this linqpad中的堆栈跟踪显示了这一点
at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
在 System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(类型 propertyType)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
在 System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& 阅读器,类型 typeToConvert,JsonSerializerOptions 选项,ReadStack& 状态,T& 值)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
在 System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& 阅读器,类型 typeToConvert,JsonSerializerOptions 选项,ReadStack& 状态,T& 值)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
在 System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(对象对象、ReadStack 和状态、Utf8JsonReader 和阅读器)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
在 System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& 阅读器,类型 typeToConvert,JsonSerializerOptions 选项,ReadStack& 状态,T& 值)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
在 System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& 阅读器,类型 typeToConvert,JsonSerializerOptions 选项,ReadStack& 状态,T& 值)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
在 System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& 阅读器、JsonSerializerOptions 选项、ReadStack& 状态)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan
1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable
1 actualByteCount)在 System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan
1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable
1 actualByteCount)at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
在 System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json,JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options) at UserQuery.Main(), line 13
在 System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options) 在 UserQuery.Main(),第 13 行
This linqpad -program has all the code needed这个linqpad -program有所有需要的代码
// Linqpad program
void Main()
{
string jsonWithConfig = "{\"config\":{\"c1\":\"All\",\"c2\":\"is peachy\"},\"message\":\"We found a Config\"}";
string jsonNoConfig = "{\"config\":\"null\",\"Message\":\"Config is null\"}";
var options = new JsonSerializerOptions{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true
};
WebResponse webResponseWithConfig = JsonSerializer.Deserialize<WebResponse>(jsonWithConfig, options);
webResponseWithConfig.Dump();
WebResponse webResponseNoConfig = JsonSerializer.Deserialize<WebResponse>(jsonNoConfig, options);
webResponseNoConfig.Dump();
}
// custom types
public class WebResponse
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Config Config { get; set; }
public string Message { get; set; }
}
public class Config
{
public string C1 { get; set; }
public string C2 { get; set; }
}
As explained in comments by MySkullCaveIsADarkPlace , your problem is that the JSON value "null"
正如MySkullCaveIsADarkPlace在评论中所解释的那样,您的问题是 JSON 值
"null"
"config":"null"
Is not null .不为空。 It is a non-null string value containing the characters
null
.它是一个包含字符
null
的非空字符串值。 A null value looks like:一个空值看起来像:
"config":null // Notice there are no quotes around the text
For confirmation, see the original JSON proposal .如需确认,请参阅原始 JSON 提案。
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.如果您无法修复 JSON 以正确表示空值,则需要编写一个自定义 JsonConverter来检查
"null"
文本值并在存在时返回null
值。 If not present, the converter should proceed with default deserialization.如果不存在,转换器应继续默认反序列化。
The question How to use default serialization in a custom System.Text.Json JsonConverter?问题如何在自定义 System.Text.Json JsonConverter 中使用默认序列化? has this answer which provides a
DefaultConverterFactory<T>
.有这个答案,它提供了
DefaultConverterFactory<T>
。 Grab it and subclass it as follows:抓住它并将其子类化如下:
NullTextValueForNullObjectConverter 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, modifiedOptions);
}
}
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 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:然后将转换器添加到
JsonSerializerOptions.Converters
,如下所示:
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:或者直接应用于
Config
属性,如下所示:
[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.请注意,目前似乎没有一种方法可以生成将转换器直接应用于
Config
类型的默认序列化。 As such, I don't recommend doing it.因此,我不建议这样做。
If you cannot fix the JSON source then in this particular case i would recommend to replace "null"
with null
using ac# string replace function如果您无法修复 JSON 源,那么在这种特殊情况下,我建议使用 ac# 字符串替换函数将
"null"
替换为null
json = json.Replace("\"null\"","null");
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.