LINQ to Json извлечение данных из полиморфного JSON

У меня есть полиморфная строка JSON. Вот как это выглядит:

{  
   "?xml":{  
      "@version":"1.0",
      "@encoding":"UTF-8"
   },
   "DataFeed":{  
      "@FeedName":"AdminData",
      "Issuer":[  
         {  
            "name":"Apple",
            "symbol":"AAPL-O",
            "active":"1",
            "securities":{  
               "Security":{  
                  "sedol":"B0XXF09",
                  "coverage":{  
                     "Coverage":{  
                        "analyst":{  
                           "@firstName":"Steve",
                           "@lastName":"Jobs",
                           "@rank":"1"
                        }
                     }
                  },
                  "symbolMappingList":{  
                     "SecuritySymbol":{  
                        "symbolset":{  
                           "id":"11",
                           "symbol":"ISIN",
                           "name":"ISIN",
                           "rixmlName":"ISIN",
                           "researchDirect":"S&P"
                        },
                        "symbol":"US44919P5XXX"
                     }
                  },
                  "symbolMapping":{  
                     "entry":{  
                        "int":"11",
                        "SecuritySymbol":{  
                           "@reference":"../../../symbolMappingList/SecuritySymbol"
                        }
                     }
                  },
                  "customFields":{  
                     "customField":[  
                        {  
                           "@name":"ADP",
                           "@type":"Textbox",
                           "values":{  
                              "value":"H0192XX"
                           }
                        },
                        {  
                           "@name":"Top 15",
                           "@type":"Dropdown, multiple choice",
                           "values":null
                        }
                     ]
                  }
               }
            }
         },
         {  
            "name":"Microsoft",
            "symbol":"MSFT-OTC",
            "active":"1",
            "securities":{  
               "Security":{  
                  "sedol":"B8FW54",
                  "coverage":{  
                     "Coverage":{  
                        "analyst":{  
                           "@firstName":"Bill",
                           "@lastName":"Gates",
                           "@rank":"1"
                        }
                     }
                  },
                  "symbolMappingList":{  
                     "SecuritySymbol":[  
                        {  
                           "symbolset":{  
                              "id":"3",
                              "symbol":"CUSIP",
                              "name":"CUSIP",
                              "rixmlName":"CUSIP",
                              "researchDirect":"S&P"
                           },
                           "symbol":"04316A1XX"
                        },
                        {  
                           "symbolset":{  
                              "id":"11",
                              "symbol":"ISIN",
                              "name":"ISIN",
                              "rixmlName":"ISIN",
                              "researchDirect":"S&P"
                           },
                           "symbol":"US04316A10XX"
                        }
                     ]
                  },
                  "symbolMapping":{  
                     "entry":[  
                        {  
                           "int":"3",
                           "SecuritySymbol":{  
                              "@reference":"../../../symbolMappingList/SecuritySymbol"
                           }
                        },
                        {  
                           "int":"11",
                           "SecuritySymbol":{  
                              "@reference":"../../../symbolMappingList/SecuritySymbol[2]"
                           }
                        }
                     ]
                  },
                  "customFields":{  
                     "customField":[  
                        {  
                           "@name":"ADP Security Code",
                           "@type":"Textbox",
                           "values":null
                        },
                        {  
                           "@name":"Top 15",
                           "@type":"Dropdown, multiple choice",
                           "values":null
                        }
                     ]
                  }
               }
            }
         }
      ]
   }
}

Кто-то однажды помог мне с расширением класса, чтобы я мог получить коды ADP. Вот класс расширения:

public static class JsonExtensions
{
    public static IEnumerable<JToken> DescendantsAndSelf(this JToken node)
    {
        if (node == null)
            return Enumerable.Empty<JToken>();
        var container = node as JContainer;
        if (container != null)
            return container.DescendantsAndSelf();
        else
            return new[] { node };
    }

    public static IEnumerable<JObject> ObjectsOrSelf(this JToken root)
    {
        if (root is JObject)
            yield return (JObject)root;
        else if (root is JContainer)
            foreach (var item in ((JContainer)root).Children())
                foreach (var child in item.ObjectsOrSelf())
                    yield return child;
        else
            yield break;
    }
}

Исходя из этого, вот мой запрос:

var compInfo = from issuer in feed.SelectTokens("DataFeed.Issuer").SelectMany(i => i.ObjectsOrSelf())
           where (string)issuer["active"] == "1"
           let security = issuer.SelectTokens("securities.Security").SelectMany(s => s.ObjectsOrSelf()).FirstOrDefault()
           let securitySymbol = issuer.SelectTokens("securities.Security.symbolMappingList")
            .SelectMany(s => s.ObjectsOrSelf()).FirstOrDefault()
           where security != null
           select new
{
               CompName = (string)issuer["name"],
               SEDOL = ((string)security["sedol"]).StartsWith("0") ? 
                String.Format("'{0}", (string)security["sedol"]) : (string)security["sedol"],
               ADP = security["customFields"]
                .DescendantsAndSelf()
                .OfType<JObject>()
                .Where(o => (string)o["@name"] == "ADP Security Code")
                .Select(o => (string)o.SelectToken("values.value"))
                .FirstOrDefault(),
               ISIN = security["symbolMappingList"]
                .DescendantsAndSelf()
                .OfType<JObject>()
                .Where(o => (string)o["SecuritySymbol.symbolset.name"] == "ISIN")
                .Select(o => (string)o.SelectToken("SecuritySymbol.symbol"))
                .FirstOrDefault()
};

Я могу получить коды ADP. Но как я могу получить коды ISIN? Я думаю, что я довольно близко, но я получаю все нули. Что мне нужно изменить, чтобы сделать эту работу?

2 ответа

Решение

Так должно быть:

   ISIN = security["symbolMappingList"]
    .DescendantsAndSelf()
    .OfType<JObject>()
    .Where(o => (string)o.SelectToken("symbolset.name") == "ISIN")
    .Select(o => (string)o.SelectToken("symbol"))
    .FirstOrDefault()

Некоторые заметки:

  1. Вы не можете использовать индексаторы для выбора глубоко вложенных токенов, как вы пытаетесь сделать с выражением o["SecuritySymbol.symbolset.name"], Индексаторы вернут только непосредственных детей. Вам нужно использовать SelectToken() выбрать внуков.

  2. Значение "SecuritySymbol" Свойство иногда является объектом:

                 "SecuritySymbol":{  
                    "symbolset":{  
    

    И иногда массив:

                 "SecuritySymbol":[  
                    {  
                       "symbolset":{  
    

    В зависимости от количества предметов в нем. Из-за этого полиморфизма вы не можете просто сделать SelectToken("SecuritySymbol.symbolset.name"), Вместо этого используйте DescendantsAndSelf() рекурсивно искать значение SecuritySymbol и его потомки (встроенные в массив или нет) для объекта с соответствующим "symbolset.name" Детский токен.

Вот обновленный запрос, который был немного оптимизирован для устранения дублирующих вызовов SelectToken а также фильтровать на "sedol" название:

var filterString = "B0XXF09"; // Null if filtering is not desired

var compInfo = from issuer in feed.SelectTokens("DataFeed.Issuer").SelectMany(i => i.ObjectsOrSelf())
               where (string)issuer["active"] == "1"
               let security = issuer.SelectTokens("securities.Security").SelectMany(s => s.ObjectsOrSelf()).FirstOrDefault()
               where security != null
               let sedol = (string)security["sedol"]
               where (sedol != null && filterString == null || sedol.Contains(filterString))
               select new
               {
                   CompName = (string)issuer["name"],
                   SEDOL = sedol.StartsWith("0") ? String.Format("'{0}", sedol) : sedol,
                   ADP = security["customFields"]
                    .DescendantsAndSelf()
                    .OfType<JObject>()
                    .Where(o => (string)o["@name"] == "ADP Security Code")
                    .Select(o => (string)o.SelectToken("values.value"))
                    .FirstOrDefault(),
                   ISIN = security["symbolMappingList"]
                    .DescendantsAndSelf()
                    .OfType<JObject>()
                    .Where(o => (string)o.SelectToken("symbolset.name") == "ISIN")
                    .Select(o => (string)o.SelectToken("symbol"))
                    .FirstOrDefault()
               };

Создайте метод расширения, который бы JToken и вернет IEnumerable<JToken>, Если массив, он вернет массив, в противном случае он вернет массив (предполагаемого) одиночного токена. Это даст вам согласованный интерфейс, независимо от того, был ли массив или отдельный объект.

public static IEnumerable<JToken> SingleOrMultiple(this JToken source)
{
    IEnumerable<JToken> arr = source as JArray;
    return arr ?? new[] { source };
}

Затем создайте свой запрос соответственно.

var query =
    from issuer in obj.SelectTokens("DataFeed.Issuer[*]")
    where (int)issuer["active"] == 1
    let security = issuer.SelectToken("securities.Security") as JObject
    where security != null
    let sedol = (string)security["sedol"]
    let customFields = JObject.FromObject(
        security.SelectTokens("customFields.customField[*]")
            .ToDictionary(
                o => (string)o["@name"],
                o => (string)o.SelectToken("values.value")
            )
    )
    let symbolMapping = JObject.FromObject(
        security.SelectToken("symbolMappingList.SecuritySymbol").SingleOrMultiple()
            .ToDictionary(
                o => (string)o.SelectToken("symbolset.name"),
                o => (string)o.SelectToken("symbolset.symbol")
            )
    )
    select new
    {
        CompanyName = (string)issuer["name"],
        Sedol = sedol.StartsWith("0") ? $"'{sedol}" : sedol,
        Adp = customFields["ADP"],
        Isin = symbolMapping["ISIN"],
    };
Другие вопросы по тегам