2

I am a newbie to JSON and I have some JSON that I trying to parse with C#.

I have tried making a class to represent by data, but the names of my properties are based on times so I’d have to hard code my data contracts. I’ve tried JSON.NET and LINQ to sort through the data, but keep getting null values due to the strange object/property.

Again I am very new to JSON so I’m sure there is a simple fix to this, I’m just not sure how to ask the question correctly. Thank you for your help.

Below is a small sample of JSON I am struggling to parse. Again thanks.

      {
  "Meta Data": {
    "1. Information": "Intraday (1min) prices and volumes",
    "2. Symbol": "MU",
    "3. Last Refreshed": "2017-05-30 16:00:00",
    "4. Interval": "1min",
    "5. Output Size": "Full size",
    "6. Time Zone": "US/Eastern"
  },
  "Time Series (1min)": {
    "2017-05-30 16:00:00": {
      "1. open": "30.7200",
      "2. high": "30.7300",
      "3. low": "30.7000",
      "4. close": "30.7000",
      "5. volume": "1390302"
    },
    "2017-05-30 15:59:00": {
      "1. open": "30.7750",
      "2. high": "30.7800",
      "3. low": "30.7200",
      "4. close": "30.7250",
      "5. volume": "380134"
    }
  }
}

Note that the "Time Series" properties come in 1min, 5min, 15min, 30min, 60min intervals, i.e. "Time Series (##min)" for various ##min.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • First thing I can think of that would help is to use date ranges as the index. For example , Instead of "Time Series (1min)", "Time Series": { "1min" { } } and then un-hyphenate the date time key so it looks like this instead: 20170530160000 – aguertin Jul 01 '17 at 17:13
  • Thanks for your comment. The problem is this is JSON I'm getting from a website, not creating. Just so I understand you, your saying to clean the properties than parse. Is there a library to change the JSON formatting itself? Thanks – user1762172 Jul 01 '17 at 17:37
  • @user1762172 Ideally the Keys inside JSON should be valid property names in the programming language of your choice. Here you can not create C# properties based on JSON you are getting. Instead you can query JObject (return type of default deserialization with JSON.Net) as shown [here](https://stackoverflow.com/questions/38670781/querying-and-filtering-array-of-jobjects-with-linq) and [here](https://stackoverflow.com/questions/17781996/linq-query-jobject) or search further based on your requirement. – Siva Gopal Jul 01 '17 at 18:39
  • 1
    1) Are there other possible `"Time Series"` properties, e.g. `""Time Series (10min)"`? Or are the set of possible time series fixed? 2) *I’ve tried JSON.NET and LINQ to sort through the data, but keep getting null values due to the strange object/property.* Can you share what you tried? – dbc Jul 01 '17 at 18:59
  • @dbc Yes "Time Series" comes in 1min, 5min, 15min, 30min, 60min intervals, so like you said "Time Series (##min)". I've tried JSON.NET to LINQ as well and get a NULLException Error too. I can get it with hard coding the values in, e.g.dynamic converted = JsonConvert.DeserializeObject(JSON2); – user1762172 Jul 01 '17 at 19:34
  • @dbc I was able to convert the JSON to a list of type and then sort that list. Thanks for your help! – user1762172 Jul 01 '17 at 19:40

4 Answers4

3

You can use this classes to deserialize that particular Json file, here I'm assuming that the two objects inside Time Series (1min) will be have the same names in every json file. But considering that they are dates, I'm pretty sure the will be different each time you will download the json.

Just to give you a little idea of what you can do with Newtonsoft Json attributes:

public class MetaData
{
    [JsonProperty("1. Information")]
    public string Information { get; set; }

    [JsonProperty("2. Symbol")]
    public string Symbol { get; set; }

    [JsonProperty("3. Last Refreshed")]
    public string LastRefreshed { get; set; }

    [JsonProperty("4. Interval")]
    public string Interval { get; set; }

    [JsonProperty("5. Output Size")]
    public string OutputSize { get; set; }

    [JsonProperty("6. Time Zone")]
    public string TimeZone { get; set; }
}

public class T1
{
    [JsonProperty("1. Information")]
    public string Open { get; set; }

    [JsonProperty("2. high")]
    public string High { get; set; }

    [JsonProperty("3. low")]
    public string Low { get; set; }

    [JsonProperty("4. close")]
    public string Close { get; set; }

    [JsonProperty("5. volume")]
    public string Volume { get; set; }
}

public class T2
{
    [JsonProperty("1. Information")]
    public string Open { get; set; }

    [JsonProperty("2. high")]
    public string High { get; set; }

    [JsonProperty("3. low")]
    public string Low { get; set; }

    [JsonProperty("4. close")]
    public string Close { get; set; }

    [JsonProperty("5. volume")]
    public string Volume { get; set; }
}

public class TimeSeries
{
    [JsonProperty("2017-05-30 16:00:00")]
    public T1 T1 { get; set; }

    [JsonProperty("2017-05-30 15:59:00")]
    public T2 T2 { get; set; }
}

public class RootObject
{
    [JsonProperty("Meta Data")]
    public MetaData MetaData { get; set; }

    [JsonProperty("Time Series (1min)")]
    public TimeSeries TimeSeries { get; set; }
}

Then, when you deserialize:

var deserializedObject = JsonConvert.DeserializeObject<RootObject>(
    File.ReadAllText("exampleFile.json"));

If you can tell us something more about you json file, we could help you better.

Francesco Bonizzi
  • 5,142
  • 6
  • 49
  • 88
  • Wow thank you! This class is much cleaner than the one I made. Like you said the time changes, but not each time you call it. Can you set the JsonProperty Dynamically? or using Reflection? The JSON file is of stock data so when you call it the times stay the same, but every day there is new information. Thank you again – user1762172 Jul 01 '17 at 19:48
  • You cannot change attributes at runtime. They are embedded into the metadata of the assembly. I could access via Reflection to a particular JsonProperty, but I would only change the internal state of a particular instance; when I'll load the attribute again, I would get a original value. – Francesco Bonizzi Jul 02 '17 at 10:07
1

You would like to deserialize your JSON series to some c# type, however it's not obvious how to do so since the JSON objects have both fixed and variable property names, none of which correspond to valid c# identifiers. Specifically:

  • Your root object has a property "Meta Data" that corresponds to a JSON object with a collection of string key/value pairs. Following the answers from this question you can bind this to a dictionary property:

    [JsonProperty("Meta Data")]
    public Dictionary<string, string> MetaData { get; set; }
    
  • In addition, your root object has an arbitrary set of properties with names like "Time Series (##min)" for various ##min with a fixed schema that corresponds to a Dictionary<DateTime, Dictionary<string, decimal>>. Because these properties have a fixed schema you cannot just use [JsonExtensionData] as proposed in Deserialize json with known and unknown fields. Instead, you can use the converter TypedExtensionDataConverter<TObject> from How to deserialize a child object with dynamic (numeric) key names? to deserialize your root object, making the time series property be as follows:

    [JsonTypedExtensionData]
    public Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>> TimeSeries { get; set; }
    

Thus you can design your root object as follows:

[JsonConverter(typeof(TypedExtensionDataConverter<RootObject>))]
public class RootObject
{
    public RootObject()
    {
        // Ensure dictionaries are allocated.
        this.MetaData = new Dictionary<string, string>();
        this.TimeSeries = new Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>>();
    }

    [JsonProperty("Meta Data")]
    public Dictionary<string, string> MetaData { get; set; }

    [JsonTypedExtensionData]
    public Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>> TimeSeries { get; set; }
}

With the TypedExtensionDataConverter<RootObject> copied verbatim from this answer.

Sample fiddle.

Note that if the set of property names "1. open", "2. high", and so on for each time series time is fixed, you can use a predefined type similar to the T1 from @FrancescoB's answer instead of a Dictionary<string, decimal>:

[JsonConverter(typeof(TypedExtensionDataConverter<RootObject>))]
public class RootObject
{
    public RootObject()
    {
        // Ensure dictionaries are allocated.
        this.MetaData = new Dictionary<string, string>();
        this.TimeSeries = new Dictionary<string, Dictionary<DateTime, TimeSeriesData>>();
    }

    [JsonProperty("Meta Data")]
    public Dictionary<string, string> MetaData { get; set; }

    [JsonTypedExtensionData]
    public Dictionary<string, Dictionary<DateTime, TimeSeriesData>> TimeSeries { get; set; }
}

public class TimeSeriesData
{
    [JsonProperty("1. open")]
    public decimal Open { get; set; }

    [JsonProperty("2. high")]
    public decimal High { get; set; }

    [JsonProperty("3. low")]
    public decimal Low { get; set; }

    [JsonProperty("4. close")]
    public decimal Close { get; set; }

    [JsonProperty("5. volume")]
    public decimal Volume { get; set; }
}

Sample fiddle #2.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • I wanted to think you for this as it seems quite detailed and complex. The problem I’m having is that I’m trying to deserialize the JSON into a table and with your code I’m unable to do that no matter what I do. For example the TimeSeriesData Properties would be the columns and the rows would be each time interval. I’ve looked into using a datatable and writing to an external Excel, but would ideally like to parse before pushing it to an external file. Any information that could even point me in the right direction would greatly appreciated, thanks again. – user1762172 Sep 27 '17 at 09:13
  • One of the best answer I found looking for a good dupe target. sadly its no high enought. Clear with a nice compilation. – Drag and Drop Feb 08 '21 at 14:36
0

You can just try using JsonCovert as follow

string json =  @"{
                              'Meta Data': {
                                '1. Information': 'Intraday (1min) prices and volumes',
                                '2. Symbol': 'MU',
                                '3. Last Refreshed': '2017-05-30 16:00:00',
                                '4. Interval': '1min',
                                '5. Output Size': 'Full size',
                                '6. Time Zone': 'US/Eastern'
                              },
                              'Time Series (1min)': {
                                '2017-05-30 16:00:00': {
                                  '1. open': '30.7200',
                                  '2. high': '30.7300',
                                  '3. low': '30.7000',
                                  '4. close': '30.7000',
                                  '5. volume': '1390302'
                                },
                                '2017-05-30 15:59:00': {
                                  '1. open': '30.7750',
                                  '2. high': '30.7800',
                                  '3. low': '30.7200',
                                  '4. close': '30.7250',
                                  '5. volume': '380134'
                                }
                              }
                            }";

var jsonConvertedData = JsonConvert.DeserializeObject(json);

This will parse json string to json object.

Goofy
  • 210
  • 1
  • 3
  • 17
0

To achieve the effect of having a dynamic name for the two properties in TimeSeries, you could parse manually the json tree (with Newtonsoft Json library):

The deserialization domain:

public class MetaData
{
    public string Information { get; set; }
    public string Symbol { get; set; }
    public DateTime LastRefreshed { get; set; }
    public string Interval { get; set; }
    public string OutputSize { get; set; }
    public string TimeZone { get; set; }
}

public class TimeSeriesInfos
{
    public double Open { get; set; }
    public double High { get; set; }
    public double Low { get; set; }
    public double Close { get; set; }
    public double Volume { get; set; }
}

public class TimeSeries
{
    public TimeSeriesInfos T1 { get; set; }
    public TimeSeriesInfos T2 { get; set; }
}

public class RootObject
{
    public MetaData MetaData { get; set; }
    public TimeSeries TimeSeries { get; set; }
}

And then deserialize it like this:

var jsonObjectTree = JsonConvert.DeserializeObject<JObject>(
    File.ReadAllText("exampleFile.json"));

const string metaDataName = "Meta Data";
const string timeSeriesName = "Time Series (1min)";
const string openName = "1. open";
const string highName = "2. high";
const string lowName = "3. low";
const string closeName = "4. close";
const string volumeName = "5. volume";

// You can obtain dynamically those two properties
string t1Name = "2017-05-30 16:00:00";
string t2Name = "2017-05-30 15:59:00";

var deserializedObject = new RootObject()
{
    MetaData = new MetaData()
    {
        Information = jsonObjectTree[metaDataName]["1. Information"].Value<string>(),
        Symbol = jsonObjectTree[metaDataName]["2. Symbol"].Value<string>(),
        LastRefreshed = DateTime.ParseExact(jsonObjectTree[metaDataName]["3. Last Refreshed"].Value<string>(), "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
        Interval = jsonObjectTree[metaDataName]["4. Interval"].Value<string>(),
        OutputSize = jsonObjectTree[metaDataName]["5. Output Size"].Value<string>(),
        TimeZone = jsonObjectTree[metaDataName]["6. Time Zone"].Value<string>()
    },
    TimeSeries = new TimeSeries()
    {
        T1 = new TimeSeriesInfos()
        {
            Open = jsonObjectTree[timeSeriesName][t1Name][openName].Value<double>(),
            High = jsonObjectTree[timeSeriesName][t1Name][highName].Value<double>(),
            Low = jsonObjectTree[timeSeriesName][t1Name][lowName].Value<double>(),
            Close = jsonObjectTree[timeSeriesName][t1Name][closeName].Value<double>(),
            Volume = jsonObjectTree[timeSeriesName][t1Name][volumeName].Value<double>()
        },
        T2 = new TimeSeriesInfos()
        {
            Open = jsonObjectTree[timeSeriesName][t2Name][openName].Value<double>(),
            High = jsonObjectTree[timeSeriesName][t2Name][highName].Value<double>(),
            Low = jsonObjectTree[timeSeriesName][t2Name][lowName].Value<double>(),
            Close = jsonObjectTree[timeSeriesName][t2Name][closeName].Value<double>(),
            Volume = jsonObjectTree[timeSeriesName][t2Name][volumeName].Value<double>()
        }
    }
};
Francesco Bonizzi
  • 5,142
  • 6
  • 49
  • 88
  • Thanks this code works great! I've learned a lot from it. The only thing I don't fully understand is how to dynamically obtain the properties of type string? The string t1Name = "2017-05-30 16:00:00"; part. Thanks. – user1762172 Jul 11 '17 at 06:56
  • I don't know the logic of your application, but if you know the `DateTime` that will be in your json, you could do something like: `myDateTime.ToString("yyyy-MM-dd hh:mm:ss")`. The thing is that now `1Name` and `t2Name` can be assigned to what values you want to correctly deserialize your json. – Francesco Bonizzi Jul 11 '17 at 07:08