0

I'm getting some JSON as a string and storing it as such:

private static List<object> history = new List<object>();

var data = JsonConvert.DeserializeObject(parsedData);
history.Add(data);

The JSON data looks something like this:

{ id: 12, data: 'my data' }

I'd like to add another method that iterates through the history list, finds an item by its ID and updates it.

What's the best way to access properties on objects like this?

Clint
  • 6,133
  • 2
  • 27
  • 48
opticon
  • 3,494
  • 5
  • 37
  • 61
  • 1
    Probably deserialize to `dynamic` so you can use [reflection to get property names or do whatever dynamic stuff you want](https://stackoverflow.com/questions/2634858/how-do-i-reflect-over-the-members-of-dynamic-object). – Erik Philips Dec 19 '17 at 19:17
  • 8
    use a legitimate type that represents the data instead of `object` – Jonesopolis Dec 19 '17 at 19:17
  • make `history` be `List` then do `history.Where(i => i.id == 12);`. For more see [Querying JSON with dynamic](https://www.newtonsoft.com/json/help/html/QueryJsonDynamic.htm). But this will throw an exception when history items don't have an id, so using an explicit type (or even a `JToken` with its dictionary methods) would be better. – dbc Dec 19 '17 at 19:24

4 Answers4

1

If the incoming JSON string always has the same fields, you can serialize it to a defined class/type.

public class JsonHistory
{
  public int id { get; set; }
  public string data { get; set; }
}

List<JsonHistory> history = new List<JsonHistory>();

var histData= JsonConvert.DeserializeObject<JsonHistory>(parsedData);
history.Add(histData);

You can also then use linq to find any matches you are looking for:

var matches = history.Where(x => x.id == 10).Select(x => x.data);
Holger Brandt
  • 4,324
  • 1
  • 20
  • 35
0

You can use dynamic instead of object, because it is easier to use properties rather than object

List<dynamic> history = new List<dynamic>();
string parsedData = "{ id: 12, data: 'my data' }";
var data = JsonConvert.DeserializeObject<dynamic>(parsedData);
history.Add(data);
history.Add(data);
foreach (var o in history)
{
    o.id = 13;
}

Also, I strongly suggest you to create a class and use it as known type. That will give you intellisense and definitely you will need that further.

lucky
  • 12,734
  • 4
  • 24
  • 46
0

The JSON.NET documentation has the following approach, that I would also suggest using:

First - deserialise to a sensible type (anonymous is fine)

var json = "{id: 12, data: 'my data'}";
var definition = new { Id = 0, Data = "" }
var deserialised = JsonConvert.DeserializeAnonymousType(json, definition);

Second - you've got your first item, so you can "trick" the compiler into letting you use the anonymous type in a list:

var history = (new[] { deserialised }).ToList();

You can then shove any other deserialised instances into that list.

Now you can do your filtering:

var itemImLookingFor = history.SingleOrDefault(x => x.Id == 10);

Once you've then got the item, you can just update it:

itemImLookingFor.Data = itemImLookingFor.Data.ToUpperCase()

I'd recommend reading this as it explains some of the shortcomings inherent in using the dynamic keyword; it's not a bad thing to do, it just has its place.

For this scenario, JSON.NET gives you all the tools you need to either deserialise to a type of your own defining (e.g. write a class that mirrors the JSON, or to an anonymous type like I've done here).

Clint
  • 6,133
  • 2
  • 27
  • 48
0

The usual approach is to either deserialize to a custom class or to deserialize to JObject which would allow you to iterate over its properties like using reflection. Both require a lot of typing.

But you can use a clever little trick and get all the benefits of a strong type without having to write the class.

Use an anonymous duck type

Add this method to your library somewhere:

static public T JsonDeserialize<T>(string input, T template)
{
    return JsonConvert.DeserializeObject<T>(input);
}

Once you have this in place, you can use it to duck-type the JSON (i.e. get the compiler to infer an anonymous type) by supplying an example instance. This allows you to use anonymous types instead of typing up a custom class.

So for example, to get the row with ID 12, you do something like this:

using System;
using System.Linq;
using Newtonsoft.Json;


public class Program
{
    static public T JsonDeserialize<T>(string input, T example)
    {
        return JsonConvert.DeserializeObject<T>(input);
    }

    public static void Main()
    {
        //Sample data with two records
        const string input = @"[ { 'id' : 12, 'data' : 'MyData' }, { 'id' : 13, 'data' : 'Another record' } ]";

        //Create an example instance so the compiler can use its anonymous type
        var example = new []
                {
                    new { id = default(int), data = default(string) }
                };


        //Pass the example as argument 2 so that the compiler can infer T. The argument itself isn't used for anything.
        var list = JsonDeserialize(input, example);

        //Now we have a strongly-typed list, without having to write a class

        //We can use LINQ or anything else that needs a strong type
        foreach ( var o in list.Where( a => a.id == 12) )
        {
            Console.WriteLine(o.id);
            Console.WriteLine(o.data);  //Intellisense works here
        }


    }
}

Output:

12
MyData

Working example on DotNetFiddle.

John Wu
  • 50,556
  • 8
  • 44
  • 80