嵌套的 JObjects 被序列化为空数组

Posted

技术标签:

【中文标题】嵌套的 JObjects 被序列化为空数组【英文标题】:Nested JObjects getting serialized as empty arrays 【发布时间】:2020-07-14 16:25:36 【问题描述】:

我遇到了一个非常奇怪的情况,我试图将第三方 API 返回的对象序列化为 JSON。我无法控制第三方 API 或其返回的对象。我试图序列化的 C# POCO 看起来像这样:

public class JobSummary 
    public Job Job  get; set;     


public class Job 
    public Status Status  get; set; 


public class Status 
    public object JobOutput  get; set; 
    public int Progress  get; set; 

根据第三方库返回的内容,我希望它会序列化为以下内容。在运行时,我可以看出JobOutput 的类型是JObject,它包含一个键(Count)和值(0)。


   job: 
       status: 
           jobOutput: 
               Count: 0
           ,
           progress: 100
       
   

在这里,工作和状态显然是对象。 progress 是 int,jobOutput 是 JObject

如果我运行以下任何变体:

    JToken.FromObject(jobSummary) JObject.FromObject(jobSummary) JObject.Parse(jobSummary)

ToString()JsonConvert.SerializeObject() 结果,我得到以下输出:


   job: 
       status: 
           jobOutput: 
               Count: []
           ,
           progress: 100
       
   

请注意,Count 已变为 []

但是如果我执行jobSummary.Status.JobOutput.ToString(),我会正确返回 0,因此我知道第三方库返回的 POCO 没有格式错误并且具有我需要的信息。

有人知道会发生什么吗?或者如何正确序列化嵌套的 JObject?

编辑:我应该澄清一下,由于我无法控制的原因,我使用的是 Newtonsoft v6.0.8,并且包含 POCO 的第三方程序集包含一个未知版本的 Newtonsoft ILMerged。我不知道这是否相关。

【问题讨论】:

如果问题是版本造成的,可以使用最新版本,并在.conf:<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="6.0.0.0" />中重定向到旧版本 @sajid 不幸的是,由于我正在使用的更大应用程序的架构,这是不可能的。:(另外我什至不确定问题是由版本造成的。 好的,你有object序列化的例子吗? @Sajid 我问题中的第一个 JSON 代表我要序列化的对象。 我们需要看到问题中的“POCO”....否则我们如何才能弄清楚发生了什么? 【参考方案1】:

你写的

我应该澄清一下,由于我无法控制的原因,我使用的是 Newtonsoft v6.0.8,并且包含 POCO 的第三方程序集包含一个未知版本的 Newtonsoft ILMerged。

这解释了您的问题。 JobOutput 包含一个全名为 Newtonsoft.Json.Linq.JObject 的对象来自与您正在使用的完全不同的 Json.NET DLL。当您的 Json.NET 版本测试以查看被序列化的对象是否为 JToken 时,它会检查 objectType.IsSubclassOf(typeof(JToken)) - 这将失败,因为 ILMerged 类型实际上不是您版本类型的子类,尽管有同名。

作为一种解决方法,您需要创建自定义 JsonConverter 逻辑,该逻辑使用外部 ToString() 方法生成输出 JSON,然后将该 JSON 写入您正在生成的 JSON 流。以下应该可以完成这项工作:

public class ForeignJsonNetContainerConverter : ForeignJsonNetBaseConverter

    static readonly string [] Names = new []
    
        "Newtonsoft.Json.Linq.JObject",
        "Newtonsoft.Json.Linq.JArray",
        "Newtonsoft.Json.Linq.JConstructor",
        "Newtonsoft.Json.Linq.JRaw",
    ;

    protected override IReadOnlyCollection<string> TypeNames  get  return Names;  

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        var json = value.ToString();
        // Fix indentation
        using (var stringReader = new StringReader(json))
        using (var jsonReader = new JsonTextReader(stringReader))
        
            writer.WriteToken(jsonReader);
        
    


public class ForeignJsonNetValueConverter : ForeignJsonNetBaseConverter

    static readonly string [] Names = new []
    
        "Newtonsoft.Json.Linq.JValue",
    ;

    protected override IReadOnlyCollection<string> TypeNames  get  return Names;  

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        var underlyingValue = ((dynamic)value).Value;
        if (underlyingValue == null)
        
            writer.WriteNull();
        
        else
        
            // JValue.ToString() will be wrong for DateTime objects, we need to serialize them instead.
            serializer.Serialize(writer, underlyingValue);
        
    


public abstract class ForeignJsonNetBaseConverter : JsonConverter

    protected abstract IReadOnlyCollection<string> TypeNames  get; 

    public override bool CanConvert(Type objectType)
    
        if (objectType.IsPrimitive)
            return false;
        // Do not use the converter for Native JToken types, only non-native types with the same name(s).
        if (objectType == typeof(JToken) || objectType.IsSubclassOf(typeof(JToken)))
            return false;
        var fullname = objectType.FullName;
        if (TypeNames.Contains(fullname))
            return true;
        return false;
    

    public override bool CanRead  get  return false;  

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        throw new NotImplementedException();
    

然后在设置中使用它们如下:

var settings = new JsonSerializerSettings

    Converters = 
    
        new ForeignJsonNetContainerConverter(), new ForeignJsonNetValueConverter()
    ,
;

var json = JsonConvert.SerializeObject(summary, Formatting.Indented, settings);

注意事项:

转换器的工作原理是假设 FullName 与 Json.NET 类型名称匹配的类型实际上是来自不同版本的 Json.NET 类型。

JValue.ToString() 返回 DateTime 对象的本地化值(有关详细信息,请参阅 here),因此我为 JValue 创建了一个单独的转换器。

我还修复了缩进以匹配。

样机小提琴here.

【讨论】:

我最终单独提出了一个类似的解决方案,但您的解决方案更加彻底和通用。这正是问题的最终结果,谢谢!

以上是关于嵌套的 JObjects 被序列化为空数组的主要内容,如果未能解决你的问题,请参考以下文章

iOS JSON 序列化空数组序列化为 nil 而不是 [ ]

将空 JSON 数组反序列化为空 TreeMap

Json.NET:将嵌套数组反序列化为强类型对象

使用 Jackson 将嵌套数组反序列化为 ArrayList

将HTML表单序列化为JavaScript对象,支持嵌套的属性和数组。

无法将当前 JSON 数组(例如 [1,2,3])反序列化为具有复杂和嵌套对象的类型