简体   繁体   English

尽管 JsonIgnoreCondition.WhenWritingNull,但如果属性具有 null 值,C# JsonSerializer.Deserialize 将失败

[英]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; }
}

What have i tried?我试过什么?

From How to ignore properties with System.Text.Json i started with JsonSerializer.Deserialize from System.Text.JsonHow 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

What is the error?错误是什么?

I hoped that using JsonSerializerOptions.DefaultIgnoreCondition would work for jsonNoConfig = {"config":"null","message":"Config is null"} .我希望使用JsonSerializerOptions.DefaultIgnoreConditionjsonNoConfig = {"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); 

Questions问题

  • How can i deserialize jsonNoConfig ?我如何反序列jsonNoConfig
  • What must i do?我必须做什么?

Update更新

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 呢?

Full stack trace完整的堆栈跟踪

The stack trace inside shows this 中的堆栈跟踪显示了这一点

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 行

LINQPad program LINQPad程序

This -program has all the code needed这个有所有需要的代码


// 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; }
}

Linqpad演示

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.因此,我不建议这样做。

Demo fiddle here .演示小提琴在这里

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM