反序列化后代对象

时间:2015-03-17 04:14:55

标签: c# json.net

我与基于JSON的API进行通信,我无法改变。它始终返回一个Response对象,其中包含不同的Result对象。通常它看起来像这样:

  

{" ver":" 2.0","结果":{"代码":0}}

对于某些命令,Result对象已经“生长”。通过添加额外的属性:

  

{" ver":" 2.0","结果":{"代码":0," hostName&# 34;:"示例"," hostPort":5000}}

我使用Newtonsoft属性来定义对象,如下所示:

 internal class RpcResponse
    {
        [JsonProperty(PropertyName = "ver")]
        public string Version { get; set; }

        [JsonProperty(PropertyName = "result")]
        public RpcResponseResult Result
        {
            get;
            set;
        }

internal class RpcResponseResult
    {
        [JsonProperty(PropertyName = "code")]
        public int Code { get; set; }
    }

internal class RpcExtendedResponseResult: RpcResponseResult
    {
        [JsonProperty(PropertyName = "hostName")]
        public string HostName { get; set; }

        [JsonProperty(PropertyName = "hostPort")]
        public int HostPort { get; set; } 

但是当反序列化反序列化时:

RpcResponse rspResponse = JsonConvert.DeserializeObject<RpcResponse>(rspString);

其Result属性始终显示为RpcResponseResult对象,即。 JsonConvert并不知道将它构造为RpcExtendedResponseResult对象。

是否有某种方法可以使用属性或转换器来恢复正确的后代对象?我觉得我错过了一些明显的东西!

2 个答案:

答案 0 :(得分:1)

这是因为对象的类型是RpcResponseResult。反序列化程序只能反序列化在指定字段类型中声明的字段。它无法确定,因为类具有“hostName”,现在它是RpcExtendedResponseResult。

如果我这样做,我可以根据需要将结果作为所有可能字段的容器,并使用默认值,然后根据需要填充另一个对象。

internal class RpcResponseResultContainer
{
    [JsonProperty(PropertyName = "code")]
    public int Code { get; set; }

    [JsonProperty(PropertyName = "hostName")]
    private string mHostName = string.Empty;
    public string HostName 
    { 
       get { return mHostName;} 
       set { mHostName = value; }
    }

    [JsonProperty(PropertyName = "hostPort")]
    private int mHostPort = -1;
    public int HostPort 
    { 
       get { return mHostPort;} 
       set { mHostPort = value;}
    }

然后,如果你真的希望得到你想要的对象,你可以在容器类中做这样的事情:

 public RpcResponseResult GetActualResponseType()
 {
     if(HostPort != -1 && !string.IsNullOrEmtpy(HostName))
     {
         return new RpcExtendedResponseResult() { Code = this.Code, HostName = this.HostName, HostPort = this.HostPort};
     }
     return new RpcResponseResult() { Code = this.Code };
 }

答案 1 :(得分:0)

首先,感谢Matthew Frontino提供了我接受的唯一答案。

但是我选择不制作单个结果容器,所以这就是我最终做的事情。

  1. 首先我开始使用此页面:How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
  2. 我使用了Alain提供的JsonCreationConverter版本。
  3. 我添加了Dribbel建议的CanWrite覆盖:

    public override bool CanWrite
    {
        get { return false; }
    }
    
  4. 我还将自己的帮助函数添加到JsonCreationConverter:

    protected bool FieldExists(string fieldName, JObject jObject)    {
        return jObject[fieldName] != null;
    }
    
  5. 然后我按如下方式创建了自己的转换器:

    class RpcResponseResultConverter : JsonCreationConverter<RpcResponseResult>
    {
        protected override RpcResponseResult Create(Type objectType, JObject jObject)
        {
            // determine extended responses
            if (FieldExists("hostName", jObject) &&
                FieldExists("hostPort", jObject) )
            {
                return new RpcExtendedResponseResult();
            }
            //default
            return new RpcResponseResult();
        }
    }
    
  6. 然后我反序列化顶级类并提供要使用的任何转换器。在这种情况下,我只提供了一个,这是针对所讨论的嵌套类:

    RpcResponse rspResponse = JsonConvert.DeserializeObject<RpcResponse>(
        rspString, 
        new JsonSerializerSettings {
            DateParseHandling = Newtonsoft.Json.DateParseHandling.None,
            Converters = new List<JsonConverter>( new JsonConverter[] {
                    new RpcResponseResultConverter()
                    })
            });
    
  7. 注意:

    • 使用JsonConvert中内置的默认转换器对转换器未明确处理的任何内容(例如顶级类)进行反序列化。
    • 仅当您可以为每个后代类标识一组唯一字段时,此方法才有效。