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()
Некоторые заметки:
Вы не можете использовать индексаторы для выбора глубоко вложенных токенов, как вы пытаетесь сделать с выражением
o["SecuritySymbol.symbolset.name"]
, Индексаторы вернут только непосредственных детей. Вам нужно использоватьSelectToken()
выбрать внуков.Значение
"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"],
};