LINQ to Json 从多态 json 检索数据

LINQ to Json data retrieval from polymorphic 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
                        }
                     ]
                  }
               }
            }
         }
      ]
   }
}

有人曾经帮助我扩展 class 以便我可以检索 ADP 代码。这是扩展名 class:

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 代码呢?我认为我非常接近,但我得到了所有空值。我需要更改什么才能使这项工作正常进行?

应该是:

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

一些注意事项:

  1. 您不能使用 indexers to select deeply nested tokens, as you try to do with the expression o["SecuritySymbol.symbolset.name"]. Indexers will only return immediate children. You need to use SelectToken() 给 select 孙子。

  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 并将 return 和 IEnumerable<JToken>。如果是数组,它将 return 数组,否则它将 return (假设的)单个标记的数组。这将为您提供一致的界面,无论是数组还是单个对象。

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"],
    };