首页 > 解决方案 > 同时反序列化多种类型并展平对象

问题描述

给定以下 JSON:

[
  {
    "ticker": "AAPL",
    "name": "Apple Inc.",
    "market": "STOCKS",
    "locale": "US",
    "currency": "USD",
    "active": true,
    "primaryExch": "NGS",
    "type": "cs",
    "codes": {
      "cik": "0000320193",
      "figiuid": "EQ0010169500001000",
      "scfigi": "BBG001S5N8V8",
      "cfigi": "BBG000B9XRY4",
      "figi": "BBG000B9Y5X2"
    },
    "updated": "2019-01-15T05:21:28.437Z",
    "url": "https://api.polygon.io/v2/reference/tickers/AAPL"
  },
  {
    "ticker": "$AEDAUD",
    "name": "United Arab Emirates dirham - Australian dollar",
    "market": "FX",
    "locale": "G",
    "currency": "AUD",
    "active": true,
    "primaryExch": "FX",
    "updated": "2019-01-25T00:00:00.000Z",
    "attrs": {
      "currencyName": "Australian dollar,",
      "currency": "AUD,",
      "baseName": "United Arab Emirates dirham,",
      "base": "AED"
    },
    "url": "https://api.polygon.io/v2/tickers/$AEDAUD"
  },
  { /* another stock */ },
  { /* another stock */ },
  { /* another FX*/ },
  { /* another stock */ },
  { /* another FX*/ }
  /* and so forth */
]

我希望反序列化为一个常见的类型列表List<Ticker>

class Ticker
{
   string Ticker {get; set;}
   string Name {get; set;}
   MarketEnum Market {get; set;}
   LocaleEnum Locale {get; set;}
   CurrencyEnum Currency {get; set;}
   bool Active {get; set;}
   PrimaryExchEnum PrimaryExch {get; set;}
   DateTimeOffset Updated {get; set;}
}

class Stock : Ticker
{
  int Type {get; set;}
  string CIK {get; set;}
  string FIGIUID {get; set;}
  string SCFIGI {get; set;}
  string CFIGI {get; set;}
  string FIGI {get; set;}
}

class ForeignExchange : Ticker
{
   BaseCurrencyEnum BaseCurrency {get; set;}
}

我有以下代码:

class TickerConvertor : CustomCreationConverter<Ticker>
{
    public override Ticker Create(Type objectType)
    {
        throw new NotImplementedException();
    }

    public Ticker Create(Type objectType, JObject jObject)
    {
        var type = (PrimaryExch)(int)jObject.Property("PrimaryExch");

        if (type == PrimaryExch.FX)
            return new ForeignExchange();
        else
            return new Stock();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        
        JObject jObject = JObject.Load(reader); // Load JObject from stream 
        var target = Create(objectType, jObject); // Create target object based on JObject 
        serializer.Populate(jObject.CreateReader(), target); // Populate the object properties 

        return target;
    }
}

但我不确定如何处理类型的子结构和类型的子结构codes的扁平化。TickerattrsForeignExchange

标签: c#json.net

解决方案


要处理正确Ticker子类的实例化和 JSON 中子结构的展平,您可以实现JsonConverter如下自定义:

class TickerConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Ticker).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);

        Ticker ticker;
        JObject childObj;
        if (obj["market"].ToObject<MarketEnum>(serializer) == MarketEnum.FX)
        {
            ticker = new ForeignExchange();
            childObj = (JObject)obj["attrs"];
        }
        else
        {
            ticker = new Stock();
            childObj = (JObject)obj["codes"];
        }

        // populate common properties from the main object
        serializer.Populate(obj.CreateReader(), ticker); 

        // also populate from the selected child object
        serializer.Populate(childObj.CreateReader(), ticker); 

        return ticker;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

要使用转换器,您可以使用 标记Ticker[JsonConverter(typeof(TickerConverter))]或将转换器传递给JsonConvert.DeserializeObject().

但是等等,在你的模型类中需要一些其他的改变才能工作:

  1. 您的所有属性都必须是public.
  2. 您不能将属性名称与其所在的类相同。在您的情况下,有问题的属性是Ticker. 您可以重命名类(例如BaseTicker)或重命名属性(例如Symbol)。但是,如果您重命名该属性,则需要将其标记为[JsonProperty("ticker")]以保留到 JSON 的映射。
  3. 在您的Stock类中,该Type属性被声明为,int但在 JSON 中它是一个字符串。这将在反序列化期间导致错误。该属性需要声明为字符串。
  4. 您已经enum为 JSON 中的许多字符串定义了各种属性。StringEnumConverter除非您对每个枚举使用 a,否则您将在反序列化期间收到转换错误。您可以通过使用 标记类中的属性或枚举本身来做到这一点[JsonConverter(typeof(StringEnumConverter))]。我会选择后者。但要考虑的另一件事是:StringEnumConverterJson.Net 附带的非常严格。如果 JSON 中出现的值在枚举中没有定义对应的值,则转换器将抛出错误。因此,您需要确保每个枚举都有一整套您可能会遇到的所有可能值。如果你想让它更宽容,你可以改用TolerantEnumConverter所示的如何在 json 反序列化期间忽略未知的枚举值?
  5. BaseCurrency类中的属性名称ForeignExchange与 JSON 不匹配。在 JSON 中,它被称为base. 所以你需要用 标记这个属性[JsonProperty("base")]

进行上述更改后,类和枚举应如下所示:

[JsonConverter(typeof(TickerConverter))]
class Ticker
{
    [JsonProperty("ticker")]
    public string Symbol { get; set; }
    public string Name { get; set; }
    public MarketEnum Market { get; set; }
    public LocaleEnum Locale { get; set; }
    public CurrencyEnum Currency { get; set; }
    public bool Active { get; set; }
    public PrimaryExchEnum PrimaryExch { get; set; }
    public DateTimeOffset Updated { get; set; }
}

class Stock : Ticker
{
    public string Type { get; set; }
    public string CIK { get; set; }
    public string FIGIUID { get; set; }
    public string SCFIGI { get; set; }
    public string CFIGI { get; set; }
    public string FIGI { get; set; }
}

class ForeignExchange : Ticker
{
    [JsonProperty("base")]
    public BaseCurrencyEnum BaseCurrency { get; set; }
}

[JsonConverter(typeof(StringEnumConverter))]
enum MarketEnum { STOCKS, FX }

[JsonConverter(typeof(StringEnumConverter))]
enum LocaleEnum { US, G }

[JsonConverter(typeof(StringEnumConverter))]
enum CurrencyEnum { USD, AUD, EUR }

[JsonConverter(typeof(StringEnumConverter))]
enum PrimaryExchEnum { NGS, FX }

[JsonConverter(typeof(StringEnumConverter))]
enum BaseCurrencyEnum { AED }

这是一个工作演示:https ://dotnetfiddle.net/0WQLHT


推荐阅读