2016-09-16 2 views
6

Вот строка json, которую у меня есть.LINQ to JSON - список настроек из динамического вложенного массива

{ 
    "?xml" : { 
     "@version" : "1.0", 
     "@encoding" : "UTF-8" 
    }, 
    "DataFeed" : { 
     "@FeedName" : "issuerDetails", 
     "SecurityDetails" : { 
      "Security" : { 
       "SecurityID" : { 
        "@idValue" : "AAPL-NSDQ", 
        "@fiscalYearEnd" : "2016-12-31T00:00:00.00" 
       }, 
       "FinancialModels" : { 
        "FinancialModel" : [{ 
          "@id" : "780", 
          "@name" : "Estimates - Energy", 
          "@clientCode" : "A", 
          "Values" : [{ 
            "@name" : "EBITDA", 
            "@clientCode" : "EBITDA", 
            "@currency" : "C$", 
            "Value" : [{ 
              "@year" : "2014", 
              "#text" : "555.64" 
             }, { 
              "@year" : "2015", 
              "#text" : "-538.986" 
             }, { 
              "@year" : "2016", 
              "#text" : "554.447" 
             }, { 
              "@year" : "2017", 
              "#text" : "551.091" 
             }, { 
              "@year" : "2018", 
              "#text" : "0" 
             } 
            ] 
           }, { 
            "@name" : "EPS", 
            "@clientCode" : "EPS", 
            "@currency" : "C$", 
            "Value" : [{ 
              "@year" : "2014", 
              "#text" : "0" 
             }, { 
              "@year" : "2015", 
              "#text" : "-1.667" 
             }, { 
              "@year" : "2016", 
              "#text" : "-1.212" 
             }, { 
              "@year" : "2017", 
              "#text" : "0.202" 
             }, { 
              "@year" : "2018", 
              "#text" : "0" 
             } 
            ] 
           }, { 
            "@name" : "CFPS", 
            "@clientCode" : "CFPS", 
            "@currency" : "C$", 
            "Value" : [{ 
              "@year" : "2014", 
              "#text" : "3.196" 
             }, { 
              "@year" : "2015", 
              "#text" : "-0.207" 
             }, { 
              "@year" : "2016", 
              "#text" : "0.599" 
             }, { 
              "@year" : "2017", 
              "#text" : "2.408" 
             }, { 
              "@year" : "2018", 
              "#text" : "0" 
             } 
            ] 
           } 
          ] 
         } 
        ] 
       } 
      } 
     } 
    } 
} 

Как я могу выбрать данные #text для пенополистирола в течение многих лет 2015, 2016, 2017? Вот запрос, который я до сих пор:

JObject jsonFeed = JObject.Parse(jsonText); 

var query = from security in jsonFeed.SelectTokens("DataFeed.SecurityDetails.Security") 
     .SelectMany(i => i.ObjectsOrSelf()) 
    let finModels = security.SelectTokens("FinancialModels.FinancialModel") 
     .SelectMany(s => s.ObjectsOrSelf()).FirstOrDefault() 
    where finModels != null 
    select new 
    { 
     FinModelClientCode = (string)finModels.SelectToken("Values[1][email protected]"), 
     FinModelYear2015 = (string)finModels.SelectToken("Values[1].Value[1][email protected]"), 
     FinModelValue2015 = (string)finModels.SelectToken("Values[1].Value[1].#text"), 
     FinModelYear2016 = (string)finModels.SelectToken("Values[1].Value[2][email protected]"), 
     FinModelValue2016 = (string)finModels.SelectToken("Values[1].Value[2].#text"), 
     FinModelYear2017 = (string)finModels.SelectToken("Values[1].Value[3][email protected]"), 
     FinModelValue2017 = (string)finModels.SelectToken("Values[1].Value[3].#text"), 
    }; 

Вот что jsonExtensions я использую:

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; 
    } 

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

Проблема заключается в том, что EPS не всегда будет в том же положении для следующей компании? Итак, я хочу, чтобы запрос для поиска кода клиента EPS & возвращал значения за упомянутые выше годы, мы надеемся, используя метод DRY. Не могли бы вы помочь мне закончить мой запрос?

ПРИМЕЧАНИЕ: Я фактически загружаю XML-строку, преобразовывая ее в JSON и затем анализируя ее.

XmlDocument doc = new XmlDocument(); 
doc.LoadXml(xmlString); 
jsonText = Newtonsoft.Json.JsonConvert.SerializeXmlNode(doc); 

JObject jsonFeed = JObject.Parse(jsonText); 

ответ

5

Я думаю, что самый простой способ будет десериализацию вашего JSON к конкретному объекту как показано ниже

var root = JsonConvert.DeserializeObject<RootObject>(jsonstring); 

Ваша модель будет

public class SecurityID 
{ 
    [JsonProperty("@idValue")] 
    public string IdValue { get; set; } 
    [JsonProperty("@iscalYearEnd")] 
    public string FiscalYearEnd { get; set; } 
} 

public class Time 
{ 
    [JsonProperty("@year")] 
    public string Year { get; set; } 
    [JsonProperty("#text")] 
    public string Text { get; set; } 
} 

public class FinancialModelItem 
{ 
    [JsonProperty("@name")] 
    public string Name { get; set; } 
    [JsonProperty("@clientCode")] 
    public string ClientCode { get; set; } 
    [JsonProperty("@currency")] 
    public string Currency { get; set; } 
    public List<Time> Value { get; set; } 
} 

public class FinancialModel 
{ 
    [JsonProperty("@id")] 
    public string Id { get; set; } 
    [JsonProperty("@name")] 
    public string Name { get; set; } 
    [JsonProperty("@clientCode")] 
    public string ClientCode { get; set; } 
    public List<FinancialModelItem> Values { get; set; } 
} 

public class FinancialModels 
{ 
    public List<FinancialModel> FinancialModel { get; set; } 
} 

public class Security 
{ 
    public SecurityID SecurityID { get; set; } 
    public FinancialModels FinancialModels { get; set; } 
} 

public class SecurityDetails 
{ 
    public Security Security { get; set; } 
} 

public class DataFeed 
{ 
    [JsonProperty("@FeedName")] 
    public string FeedName { get; set; } 
    public SecurityDetails SecurityDetails { get; set; } 
} 

public class Xml 
{ 
    [JsonProperty("@version")] 
    public string Version { get; set; } 
    [JsonProperty("@encoding")] 
    public string Encoding { get; set; } 
} 

public class RootObject 
{ 
    [JsonProperty("?xml")] 
    public Xml Xml { get; set; } 
    public DataFeed DataFeed { get; set; } 
} 

И ваш запрос будет теперь

var result = root.DataFeed.SecurityDetails.Security.FinancialModels.FinancialModel 
       .FirstOrDefault()?.Values 
       .FirstOrDefault(x => x.Name == "EPS") 
       .Value 
       .Where(x => new[] { "2015", "2016", "2017" }.Contains(x.Year)) 
       .Select(x => x.Text) 
       .ToList(); 
1

В случае, если вы уверены, что это будет формат JSON, вы можете создать класс, который представляет собой объект JSON, и пусть некоторые библиотеки (например, JconConvert), чтобы сделать синтаксический анализ. Это должно быть легко оттуда.

+0

Я предпочел бы использовать метод я уже использую. Как я уже говорил, JSON будет динамичным. Таким образом, свойства объекта могут не быть статическими для следующего объекта. Разве мы не можем использовать какой-то метод, который уже упоминался? –

2

насчет:

var jsonFeed = JObject.Parse(jsonText); 
var epsToken = jsonFeed.SelectToken("$..Values[?(@[email protected]=='EPS')]");    
var year2014 = epsToken.SelectToken("Value[?(@[email protected]=='2014')].#text").ToString(); 
var year2015 = epsToken.SelectToken("Value[?(@[email protected]=='2015')].#text").ToString(); 
var year2016 = epsToken.SelectToken("Value[?(@[email protected]=='2016')].#text").ToString(); 
var year2017 = epsToken.SelectToken("Value[?(@[email protected]=='2017')].#text").ToString(); 

Более общий подход, который будет выбирать все годы и значения:

var jsonFeed = JObject.Parse(jsonText); 
var epsToken = jsonFeed.SelectToken("$..Values[?(@[email protected]=='EPS')]");    
var years = epsToken.SelectToken("Value") 
        .Select(i => new 
        { 
         Year = i.Value<string>("@year"), 
         Value = i.Value<decimal>("#text") 
        }); 

$.. означает, что мы будем искать из начало документа итерации через все узлы и поиск токена Values, который @name равен EPS. В основном, между ?( и ) вы входите в условие, в котором должны совпадать маркеры. @ означает текущий узел, поэтому @[email protected] переводит на current node which has child node with name '@name' (который мы сравнили с EPS в примере).

Дополнительную информацию о JPath можно найти здесь: http://goessner.net/articles/JsonPath.


Заметили ли вы обновили свой ответ, который вы имеете дело с XML, так что основы остаются прежними:

XmlDocument doc = new XmlDocument(); 
doc.LoadXml(xmlString); 
var epsNode = doc.SelectSingleNode("//Values[@name='EPS']"); 
var years = epsNode.SelectNodes("Value") 
        .Cast<XmlNode>() 
        .Select(i => new 
        { 
         Year = i.Attributes["year"].Value, 
         Value = decimal.Parse(i.InnerText) 
        }); 

Не проверял его на XML. Кроме того, имейте в виду, что i.Attributes["year"] может быть null, поэтому проверьте это.

2

Вы начали с XML-данных ... почему бы вам просто не обработать его как XML-данные.

var name = "EPS"; 
var years = new[] { "2015", "2016", "2017" }; 
var xpath = $"//Values[@name='{name}']/Value[{String.Join(" or ", years.Select(y => $"@year='{y}'"))}]"; 
var values = doc.XPathSelectElements(xpath).Select(e => (decimal)e); 

В противном случае, если вы должны настаивать на работу с ним, как JSON, то вы можете сделать это:

var name = "EPS"; 
var years = new[] { "2015", "2016", "2017" }; 
var jpath = $"$..Values[?(@[email protected]=='{name}')].Value[?({String.Join(" || ", years.Select(y => $"@[email protected]=='{y}'"))})].#text"; 
var values = jsonFeed.SelectTokens(jpath).Select(v => (decimal)v);