【问题标题】:Deserialize Json Object to polymorphic C# object without typeNameHandling将 Json 对象反序列化为没有 typeNameHandling 的多态 C# 对象
【发布时间】:2013-05-07 08:37:42
【问题描述】:

我的问题是我想将一个json对象反序列化为一个C#对象,但诀窍是C#对象包含List,而这个抽象类是另外10个类的超类。

public sealed class SearchAPIResult
{
    public string Status;

    public SearchAPIQuery Query;

    public SearchResults Result;

    public SearchAPIResult()
    {

    }

    public SearchAPIResult(string status)
    {
        Status = status;
    }
}

SearchAPIResult 是:

public sealed class SearchResults
{
    public string TextAnswer;

    public List<APIResultWidget> Items;

    public SearchResults()
    {
        Items = new List<APIResultWidget>();
    }
}

这里的对象APIResultWidget是一个抽象类,有大约10个类继承自它。

问题在于 JSON 对象没有自动的东西(如 JSON.NET 中的 typeNameHandling)来引导反序列化器将 10 个派生类的对象转换为哪个对象。相反,对象由两个字段标记:类型和子类型...如下所示

{
    "Status": "OK",
    "Query": {
        "Query": "this is a query",
        "QueryLanguage": "EN"
    },
    "Result": {
        "TextAnswer": "This is your text answer",
        "Items": [{
                "Type": "list",
                "SubType": null,
                "Title": null,
                "Items": []
            }, {
                "Type": "text",
                "Content": "this is some content"
            }
        ]
    }
}

在前面的json对象中,Result列表包含两个对象,一个:

{
    "Type": "list",
    "SubType": null,
    "Title": null,
    "Items": []
}

映射到一个listWidget类型的类(继承自抽象APIResultWidget和两个:

{
    "Type": "text",
    "Content": "this is some content"
}

映射到类textWidget,它也继承自同一个抽象类

当我使用 Json.NET 方式时

SearchAPIResult converted = (SearchAPIResult)JsonConvert.DeserializeObject(json, typeof(SearchAPIResult), new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });

它会抛出以下异常:

无法创建 Kngine.API.APIResultWidget 类型的实例。类型是接口或抽象类,不能实例化。路径 'Result.Items[0].Type',第 1 行,位置 136。

我猜有一种自定义方式指出该类型由字段 Type 和 SubType 定义,并为 Converter 提供自定义类型注释器,对吗?

【问题讨论】:

    标签: c# json polymorphism json.net servicestack


    【解决方案1】:

    实际上我实现的解决方案是非常基本的解决方案,我从 System.Web.Extentions 扩展了 JavaScriptConverter 类,我实现了 deseriealize 方法,它自动将原始对象中的每个小 json 对象作为字典接收,我可以根据对象本身填写字段,这有点手动方式,但这是我能想出和工作的唯一解决方案,自定义类实现如下所示:

    class myCustomResolver  : JavaScriptConverter
    
    {
        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
        {
            if (type == typeof(APIResultWidget))
            {
                switch ((string)dictionary["Type"])
                {
                    case "weather":
                        {
                            WeatherWidget x = new WeatherWidget();
                            x.Location = (string)dictionary["Location"];
                            x.Current = (CurrentWeather)dictionary["Current"];
                            //x.Forcast = (List<WeatherForcastItem>)dictionary["Forcast"];
    
                            System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Forcast"]);
                            foreach (var item in itemss)
                            {
                                x.Forcast.Add(serializer.ConvertToType<WeatherForcastItem>(item));
                            }
    
                            return x;
                        };
                    case "text":
                        {
                            TextWidget x = new TextWidget();
                            x.Content = (string)dictionary["Content"];
                            return x;
                        }; 
                    case "keyValueText":
                        {
                            KeyValueTextWidget x = new KeyValueTextWidget();
                            x.Key = (string)dictionary["Key"];
                            x.Key = (string)dictionary["Value"];
                            x.Key = (string)dictionary["ValueURL"];
                            return x;
                        };
                    case "keyValuesText":
                        {
                            KeyValuesTextWidget x = new KeyValuesTextWidget();
                            x.Key = (string)dictionary["Key"];
                            //x.Values = (List<ValueItem>)dictionary["ValueItem"];
    
                            System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["ValueItem"]);
                            foreach (var item in itemss)
                            {
                                x.Values.Add(serializer.ConvertToType<ValueItem>(item));
                            }
    
    
                            return x;
    
    
                        }; 
                    case "url":
                        {
                            URLWidget x = new URLWidget();
                            x.ThumbnailImageURL = (string)dictionary["ThumbnailImageURL"];
                            x.Title = (string)dictionary["Title"];
                            x.URL = (string)dictionary["URL"];
                            x.HTMLContent = (string)dictionary["HTMLContent"];
                            return x;
    
                        }; 
                    case "map":
                        {
                            MapWidget x = new MapWidget();
                            System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Pins"]);
                            foreach (var item in itemss)
                            {
                                x.Pins.Add(serializer.ConvertToType<MapPoint>(item));
                            }
    
                            //x.Pins = (List<MapPoint>)dictionary["Pins"];
                            return x;
    
                        }; 
                    case "image":
                        {
                            ImageWidget x = new ImageWidget();
                            x.Title = (string)dictionary["Title"];
                            x.ImageURL = (string)dictionary["ImageURL"];
                            x.ThumbnailURL = (string)dictionary["ThumbnailURL"];
                            x.PageURL = (string)dictionary["PageURL"];
                            return x;
                        }; 
                    case "html":
                        {
                            HTMLWidget x = new HTMLWidget();
                            x.Title = (string)dictionary["Title"];
                            x.HTML = (string)dictionary["HTML"];
                            return x;
    
    
                        }; 
                    case "entity":
                        {
                            EntityWidget x = new EntityWidget();
                            x.SubType = (string)dictionary["SubType"];
                            x.Title = (string)dictionary["Title"];
                            x.Abstract = (string)dictionary["Abstract"];
                            x.ImageURL = (string)dictionary["ImageURL"];
                            x.Url = (string)dictionary["Url"];
                            return x;
    
                        }; 
                    case "chart":
                        {
                            ChartWidget x = new ChartWidget();
                            x.Title = (string)dictionary["Title"];
                            //x.Categories = (List<string>)dictionary["Categories"];
                            System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Categories"]);
                            foreach (var item in itemss)
                            {
                                x.Categories.Add(serializer.ConvertToType<string>(item));
                            }
    
    
    
                            System.Collections.ArrayList itemss2 = ((System.Collections.ArrayList)dictionary["Data"]);
                            foreach (var item in itemss2)
                            {
                                x.Data.Add(serializer.ConvertToType<ChartsData>(item));
                            }
    
                            //x.Data = (List<ChartsData>)dictionary["Data"];
                            return x;
                        }; 
                    case "businessEntity":
                        {
                            BusinessEntityWidget x = new BusinessEntityWidget();
                            x.SubType = (string)dictionary["SubType"];
                            x.Title = (string)dictionary["Title"];
                            x.Abstract = (string)dictionary["Abstract"];
                            x.ImageURL = (string)dictionary["ImageURL"];
                            x.URL = (string)dictionary["URL"];
                            //x.Attributes = (List<KeyValueTextWidget>)dictionary["Attributes"];
                            System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Attributes"]);
                            foreach (var item in itemss)
                            {
                                x.Attributes.Add(serializer.ConvertToType<KeyValueTextWidget>(item));
                            }
    
                            x.Address = (string)dictionary["Address"];
                            x.Phone = (string)dictionary["Phone"];
                            x.Lat = (double)dictionary["Lat"];
                            x.Lng = (double)dictionary["Lng"];
    
    
                            System.Collections.ArrayList itemss2 = ((System.Collections.ArrayList)dictionary["OtherURLs"]);
                            foreach (var item in itemss2)
                            {
                                x.OtherURLs.Add(serializer.ConvertToType<URLWidget>(item));
                            }
                            //x.OtherURLs = (List<URLWidget>)dictionary["OtherURLs"];
    
                            return x;
    
    
    
                        }; 
    
                    case "list":
                        {
                            switch ((string)dictionary["SubType"])
                            {
                                case null:
                                    {
                                        ListWidget x = new ListWidget();
                                        x.Title = (string)dictionary["Title"];
                                        System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Items"]);
                                        foreach (var item in itemss)
                                        {
                                            x.Items.Add(serializer.ConvertToType<APIResultWidget>(item));
                                        }
                                        return x;
    
                                    }; 
                                case "videos":
                                    {
                                        ListOfVideosWidget x = new ListOfVideosWidget();
    
                                        System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Items"]);
                                        foreach (var item in itemss)
                                        {
                                            x.Items.Add(serializer.ConvertToType<URLWidget>(item));
                                        }
                                        return x;
                                    }; 
                                case "images":
                                    {
                                        ListOfImagesWidget x = new ListOfImagesWidget();
    
    
                                        System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Items"]);
                                        foreach (var item in itemss)
                                        {
                                            x.Items.Add(serializer.ConvertToType<ImageWidget>(item));
                                        }
                                        return x;
                                    }; 
                                case "webResults":
                                    {
    
                                        ListOfWebsitesWidget x = new ListOfWebsitesWidget();
    
                                        System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Items"]);
                                        foreach (var item in itemss)
                                        {
                                            x.Items.Add(serializer.ConvertToType<URLWidget>(item));
                                        }
                                        return x;
                                    }; 
                                case "businesses":
                                    {
                                        ListOfBusinessesWidget x = new ListOfBusinessesWidget();
                                        x.Title = (string)dictionary["Title"];
                                        System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["Items"]);
                                        foreach (var item in itemss)
                                        {
                                            x.Items.Add(serializer.ConvertToType<BusinessEntityWidget>(item));
                                        }
                                        return x;
                                    }; 
    
    
    
                            }
                        }; break;
    
    
                }
            }
    
            else //in case of objects not inheriting from the abstract class, in this case we identify each one by something else, not "type"
            {
                if (dictionary.ContainsKey("Day")) //WeatherForcastItem
                {
                    WeatherForcastItem x = new WeatherForcastItem();
                    x.Day = (string)dictionary["Day"];
                    x.Hi = (string)dictionary["Hi"];
                    x.Lo = (string)dictionary["Lo"];
                    x.Status = (string)dictionary["Status"];
                    x.IconURL = (string)dictionary["IconURL"];
                    return x;
    
                }
                else if (dictionary.ContainsKey("Temprature")) // CurrentWeather
                {
                    CurrentWeather x = new CurrentWeather();
                    x.Temprature = (string)dictionary["Temprature"];
                    x.Status = (string)dictionary["Status"];
                    x.WindSpeed = (string)dictionary["WindSpeed"];
                    x.WindDirection = (string)dictionary["WindDirection"];
                    x.Humidity = (string)dictionary["Humidity"];
                    x.IconURL = (string)dictionary["IconURL"];
                    x.IsNight = (string)dictionary["IsNight"];
                    return x;
    
                }
                else if (dictionary.ContainsKey("Lat")) //MapPoint
                {
                    MapPoint x = new MapPoint();
                    x.Title = (string)dictionary["Title"];
                    x.Lat = (double)dictionary["Lat"];
                    x.Lng = (double)dictionary["Lng"];
                    return x;
                }
                else if (dictionary.ContainsKey("Value")) //ValueItem
                {
                    ValueItem x = new ValueItem();
                    x.Value = (string)dictionary["Value"];
                    x.ValueURL = (string)dictionary["ValueURL"];
                    return x;
                }
                else if (dictionary.ContainsKey("name")) //ChartsData
                {
                    ChartsData x = new ChartsData();
                    x.name = (string)dictionary["name"];
                    System.Collections.ArrayList itemss = ((System.Collections.ArrayList)dictionary["name"]);
                    foreach (var item in itemss)
                    {
                        x.values.Add(serializer.ConvertToType<string>(item));
                    }
                    return x;
                }
            }
            return null;
        }
    
        public override IDictionary<string, object> Serialize(
     object obj,
     JavaScriptSerializer serializer)
        { return null; }
    
        private static readonly Type[] _supportedTypes = new[]
    {
        typeof( APIResultWidget )
    };
    
        public override IEnumerable<Type> SupportedTypes
        {
            get { return _supportedTypes; }
        }
    
    }
    

    应该将每个 json 对象映射到它的正确类,那么反序列化器的使用就相当容易了:

    var serializer = new JavaScriptSerializer();
    serializer.RegisterConverters(new[] { new myCustomResolver() });
    var dataObj = serializer.Deserialize<SearchAPIResult>(response);
    

    这解决了我在使用抽象类时遇到的反序列化问题,因为它首先完全解决了这些类如何相互关联的问题。我不知道这是否是最正确的解决方案,但至少它解决了我的问题

    【讨论】:

      【解决方案2】:

      首先,这是反序列化对象的更好方法:

      var converted = JsonConvert.DeserializeObject<SearchAPIResult>(json);
      

      无论如何,我认为您的问题是您试图指示序列化程序仅使用 APIResultWidget 列表反序列化 Item 属性。你能用那个课程更新你的帖子吗?我们可以更轻松地为您提供帮助。

      【讨论】:

      • APIResultWidget 只是一个实际上是空的抽象类
      【解决方案3】:

      默认情况下,ServiceStack 只序列化公共属性。要让它序列化公共字段,你需要配置它:

      JsConfig.IncludePublicFields = true;
      

      【讨论】:

      • 不是序列化问题,是反序列化问题,我无法操纵序列化过程。
      猜你喜欢
      • 1970-01-01
      • 2011-03-09
      • 2020-12-02
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-05
      相关资源
      最近更新 更多