C# JsonSerializer.如果尽管有JsonIgnoreCondition.WhenWritingNull,但属性仍具有空值,则反序列化失败

2fjabf4q  于 2022-12-24  发布在  C#
关注(0)|答案(2)|浏览(236)

从外部Web服务接收

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

我想将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; }
}

我试过什么?

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);

这适用于jsonWithConfig,这并不奇怪,因为json可以反序列化为WebResponse类型。

错误是什么?

我希望使用JsonSerializerOptions.DefaultIgnoreCondition可以用于jsonNoConfig = {"config":"null","message":"Config is null"},但是使用DeserializeUnableToConvertValuejsonNoConfig的反序列化失败

string jsonNoConfig = 
       {"config":"null","message":"Config is null"} 
WebResponse webResponseNoConfig = 
            JsonSerializer.Deserialize<WebResponse>(jsonNoConfig, options);

问题

  • 我如何反序列化jsonNoConfig
  • 我该怎么办?

更新

MySkullCaveIsADarkPlace指出config的值应该是null,而不是"null"。更改后,上面的代码可以按预期工作。
但是有没有办法也处理带引号的null,比如{"config":"null", ...}

完整堆栈跟踪

linqpad内部的堆栈跟踪显示了这一点
在系统.文本. json.抛出助手.抛出jsonException_反序列化无法转换为值(类型属性类型)
at System. Text. Json. Serialization. Converters. ObjectDefaultConverter '1.OnTryRead(对象读取器和读取器、类型类型到转换、JsonSerializerOptions选项、读取堆栈和状态、T和值)
at系统.文本. Json.序列化. JsonConverter '1.TryRead(Utf8JsonReader和读取器,类型类型到转换,JsonSerializerOptions选项,读取堆栈和状态,T和值)
at系统.文本. Json.序列化.元数据. JsonPropertyInfo'1.读取JsonAndSetMember(对象对象,读取堆栈和状态,Utf8JsonReader和读取器)
at System. Text. Json. Serialization. Converters. ObjectDefaultConverter '1.OnTryRead(对象读取器和读取器、类型类型到转换、JsonSerializerOptions选项、读取堆栈和状态、T和值)
at系统.文本. Json.序列化. JsonConverter '1.TryRead(Utf8JsonReader和读取器,类型类型到转换,JsonSerializerOptions选项,读取堆栈和状态,T和值)
位于系统.文本. Json.序列化. JsonConverter '1.ReadCore(Utf8JsonReader和读取器、JsonSerializerOptions选项、读取堆栈和状态)
在系统.文本. Json. Json序列化程序.读取范围[T值](只读范围1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable 1实际字节计数)
在系统.文本. json. json串行化器. ReadFromSpan [TValue](只读跨度json,类型信息json类型信息)
位于UserQuery.Main()第13行的System.Text.Json.JsonSerializer.Deserialize [TValue](字符串json、JsonSerializerOptions选项)中

LINQPad程序

这个linqpad程序包含了所有需要的代码

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

cl25kdpy

cl25kdpy1#

正如MySkullCaveIsADarkPlace在注解中所解释的,您的问题是JSON值"null"

"config":"null"

Isnot null。它是包含字符null的非空字符串值。空值如下所示:

"config":null // Notice there are no quotes around the text

有关确认信息,请参见original JSON proposal
如果您无法修复JSON以正确表示空值,则需要编写custom JsonConverter来检查"null"文本值并返回null(如果存在)。如果不存在,则转换器应继续默认的反序列化。
问题 How to use default serialization in a custom System.Text.Json JsonConverter?this answerthis answer提供了一个DefaultConverterFactory<T>,抓取它并将其子类化如下:
空对象的空文本值转换器

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

默认转换器工厂

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

JSON序列化程序扩展

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

然后将转换器添加到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);

或者直接应用于Config属性,如下所示:

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]   
[JsonConverter(typeof(NullTextValueForNullObjectConverter<Config>))]
public Config Config { get; set; }

请注意,目前似乎没有一种方法可以生成一个默认的序列化,即直接将转换器应用于Config类型,因此,我不推荐这样做。
演示小提琴here

3xiyfsfu

3xiyfsfu2#

如果您无法修复JSON源代码,那么在这种特殊情况下,我建议使用c#字符串替换函数将"null"替换为null

json = json.Replace("\"null\"","null");

相关问题