3

I'm trying to figure out how to loop through some JSON data to insert it into a DataGrid, to serve as an event log. However trying to parse JSON is giving me a headache.

I'm trying to use JSON.NET by Newtonsoft.

The JSON string looks something like this;

{
    "result" : "ok",
    "response" : {
    "first" : 0,
    "count" : 190,
    "now" : 1509822169,
    "events" : [
    {
        "index" : 0,
        "time" : 1509815973,
        "name" : "SessionCreated",
        "attributes" : {}
    },
    {
        "index" : 1,
        "time" : 1509815973,
        "name" : "PlayerJoined",
        "refid" : 32896,
        "attributes" : {}
        "Name" : "Dealman",
        "SteamId" : "76561197986562417"
    },
    {
        "index" : 2,
        "time" : 1509815973,
        "name" : "Authenticated",
        "refid" : 32896,
        "attributes" : {}
    },
    {
        "index" : 3,
        "time" : 1509815973,
        "name" : "StateChanged",
        "attributes" : {}
        "PreviousState" : "None",
        "NewState" : "Lobby"
    },
    {
        "index" : 4,
        "time" : 1509815998,
        "name" : "PlayerChat",
        "refid" : 32896,
        "attributes" : {
            "Message" : "This is a message"
        }
    },
    {
        "index" : 5,
        "time" : 1509816030,
        "name" : "StateChanged",
        "attributes" : {}
        "PreviousState" : "Lobby",
        "NewState" : "Loading"
    },
    {
        "index" : 6,
        "time" : 1509816030,
        "name" : "SessionSetup",
        "attributes" : {}
        "GridSize" : 22,
        "MaxPlayers" : 22,
        "PracticeLength" : 0,
        "QualifyLength" : 15,
        "RaceLength" : 6,
        "Flags" : -1316224232,
        "TrackId" : -52972612,
        "GameMode" : -1958878043
    },
    {
        "index" : 7,
        "time" : 1509816030,
        "name" : "StageChanged",
        "attributes" : {
            "PreviousStage" : "Practice1",
            "NewStage" : "Qualifying1",
            "Length" : 15
        }
    },
    {
        "index" : 8,
        "time" : 1509816046,
        "name" : "StateChanged",
        "attributes" : {
            "PreviousState" : "Loading",
            "NewState" : "Race"
        }
    },
    {
        "index" : 9,
        "time" : 1509816046,
        "name" : "ParticipantCreated",
        "refid" : 32896,
        "participantid" : 0,
        "attributes" : {
            "Name" : "Dealman",
            "IsPlayer" : 1,
            "VehicleId" : 1764851930,
            "LiveryId" : 54
        }
    }]}
}

I've been trying to do something like this;

dynamic jsonObj = JsonConvert.DeserializeObject(messageContent);
foreach(var item in jsonObj)
{
    Trace.WriteLine(item.result);
}

I've also tried some other methods like using lists, but I simply can't get it to work and I keep getting a RuntimeBinderException. I've been stuck at this for so long now that I'm starting to consider to just use regex as this seems to be more work than it's worth.

What am I missing and/or misunderstanding here? :(

3 Answers 3

1
   public static ExpandoObject ToExpando(string json)
    {
        if (string.IsNullOrEmpty(json))
            return null;
        return (ExpandoObject)ToExpandoObject(JToken.Parse(json));
    }


    private static object ToExpandoObject(JToken token)
    {

        switch (token.Type)
        {
            case JTokenType.Object:
                var expando = new ExpandoObject();
                var expandoDic = (IDictionary<string, object>)expando;
                foreach(var prop in token.Children<JProperty>())
                    expandoDic.Add(prop.Name, ToExpandoObject(prop.Value));
                return expando;
            case JTokenType.Array:
                return token.Select(ToExpandoObject).ToList();

            default:
                return ((JValue)token).Value;
        }
    }

    var ebj = ToExpando(json);
    var name = (ebj as dynamic).response.events[1].name;

A better (easier to use) version using dynamic.

Sign up to request clarification or add additional context in comments.

1 Comment

Oooh, very nice! And here I thought I was just gonna be best of "bruteforcing" my way using SelectToken... Will do some more testing but it seems to work nicely so far! Thanks a bunch
1
    var json = @"...";

    var obj = ToObject(json) as IDictionary<string, object>;
    var resp = obj["response"] as IDictionary<string, object>;
    var events = resp["events"];

    public static object ToObject(string json)
    {
        if (string.IsNullOrEmpty(json))
            return null;
        return ToObject(JToken.Parse(json));
    }

    private static object ToObject(JToken token)
    {
        switch (token.Type)
        {
            case JTokenType.Object:
                return token.Children<JProperty>()
                            .ToDictionary(prop => prop.Name,
                                          prop => ToObject(prop.Value),
                                          StringComparer.OrdinalIgnoreCase);

            case JTokenType.Array:
                return token.Select(ToObject).ToList();

            default:
                return ((JValue)token).Value;
        }
    }

The json string is converted to a Dictionary where items are simple values, lists or nested dictionaries. Much easier to iterate on dictionaries than use reflection.

BTW, there is a bug in your json - missing comma after "attributes" : {}. Also, having both "name" and "Name" as properties is not a good idea.

1 Comment

Thanks! As for the missing comma - is it necessary though since it's the last entry in the array? Also, the HTTP API that return this data is embedded into the game. Thus I am unfortunately unable to make any changes to it. I've added your code and I'm trying to figure out how it works. Looping through resp["events"] gives me this; [first, 0] [count, 302] [now, 1509826920] [events, System.Collections.Generic.List`1[System.Object]]
1

The problem you're having is because result is a top-level property that isn't part of a collection, so trying to access it using a loop doesn't make any sense. This prints ok as expected:

using Newtonsoft.Json;
using System;
using System.IO;

namespace SO47114632Core
{
    class Program
    {
        static void Main(string[] args)
        {
            var content = File.ReadAllText("test.json");
            dynamic json = JsonConvert.DeserializeObject(content);
            Console.WriteLine(json.result);
        }
    }
}

I added test.json, with the JSON from your question, in the root folder of the project, setting Copy to Output Directory to Always in the file's properties:

File Properties

7 Comments

For me it returns 2 errors, am I missing some references or something perhaps? Exception thrown: 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' in System.Core.dll Exception thrown: 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' in mscorlib.dll
@Dealman I've tried with both a normal console app and a .NET Core console app, and both are working correctly for me. In fact, the code is identical, so I'll edit my answer to provide the full program.
@Dealman Done. If you're still having problems, I'd recommend creating a fresh solution and trying out the above. If that works, you know it's something else to do with your current project.
I'm sure it works, I'm just not quite sure what the RuntimeBinderException means - it doesn't really tell me much. You reckon it might be because I do this in an async task? Or inside a using statement?
string test = json.result; Trace.WriteLine(test); Doing it like that works. I guess Trace.WriteLine works a fair bit differently than Console.WriteLine under the hood?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.