JContainer, JObject, JToken and Linq confusion

59,086

Solution 1

JContainer is a base class for JSON elements that have child items. JObject, JArray, JProperty and JConstructor all inherit from it.

For example, the following code:

(JObject)JsonConvert.DeserializeObject("[1, 2, 3]")

Would throw an InvalidCastException, but if you cast it to a JContainer, it would be fine.

Regarding your original question, if you know you have a JSON object at the top level, you can just use:

var jsonWork = JObject.Parse(json);
var jsonObject1 = jsonWork["Object1"];

Solution 2

You don't really need to worry about JContainer in most cases. It is there to help organize and structure LINQ-to-JSON into well-factored code.

The JToken hierarchy looks like this:

JToken             - abstract base class     
   JContainer      - abstract base class of JTokens that can contain other JTokens
       JArray      - represents a JSON array (contains an ordered list of JTokens)
       JObject     - represents a JSON object (contains a collection of JProperties)
       JProperty   - represents a JSON property (a name/JToken pair inside a JObject)
   JValue          - represents a primitive JSON value (string, number, boolean, null)

So you see, a JObject is a JContainer, which is a JToken.

Here's the basic rule of thumb:

  • If you know you have an object (denoted by curly braces { and } in JSON), use JObject
  • If you know you have an array or list (denoted by square brackets [ and ]), use JArray
  • If you know you have a primitive value, use JValue
  • If you don't know what kind of token you have, or want to be able to handle any of the above in a general way, use JToken. You can then check its Type property to determine what kind of token it is and cast it appropriately.

Solution 3

Most examples have simple json and I've googled "C# Newtonsoft parse JSON" more than once.

Here's a bit of a json file I was just asked to parse for a csv. The company name value is nested within many arrays / objects so it is semi-complicated in that regard.

{
  "page": {
    "page": 1,
    "pageSize": 250
  },
  "dataRows": [
    {
      "columnValues": {
        "companyName": [
          {
            "name": "My Awesome Company",
          }
        ]
      }
    }
  ]
}
            var jsonFilePath = @"C:\data.json";
            var jsonStr = File.ReadAllText(jsonFilePath);

            // JObject implementation for getting dataRows JArray - in this case I find it simpler and more readable to use a dynamic cast (below)
            //JObject jsonObj = JsonConvert.DeserializeObject<JObject>(jsonStr);
            //var dataRows = (JArray)jsonObj["dataRows"];

            var dataRows = ((dynamic)JsonConvert.DeserializeObject(jsonStr)).dataRows;

            var csvLines = new List<string>();

            for (var i = 0; i < dataRows.Count; i++)
            {
                var name = dataRows[i]["columnValues"]["companyName"][0]["name"].ToString();

                // dynamic casting implemntation to get name - in this case, using JObject indexing (above) seems easier
                //var name2 = ((dynamic)((dynamic)((dynamic)dataRows[i]).columnValues).companyName[0]).name.ToString();

                csvLines.Add(name);
            }

            File.WriteAllLines($@"C:\data_{DateTime.Now.Ticks}.csv", csvLines);
Share:
59,086
Jazimov
Author by

Jazimov

Updated on July 05, 2022

Comments

  • Jazimov
    Jazimov almost 2 years

    I am having trouble understanding when to use JContainer, JObject, and JToken. I understand from the "standards" that JObject is composed of JProperties and that JToken is the base abstract class for all of the JToken types, but I don't understand JContainer.

    I am using C# and I just bought LinqPad Pro 5.

    I have a JSON data source in a file, so I'm deserializing that file's contents successfully using this statement:

    string json;
    using (StreamReader reader = new StreamReader(@"myjsonfile.json"))
    {
        json = reader.ReadToEnd();
    }
    

    At that point, I take the JSON string object and deserialize it to a JObject (and this might be my mistake--perhaps I need to make jsonWork a JToken or JContainer?):

    JObject jsonWork = (JObject)JsonConvert.DeserializeObject(json);
    

    In my JSON data (the string represented by JSON), I have three objects--the top-level object look similar to this:

    {
      "Object1" : { ... },
      "Object2" : { ... },
      "Object3" : { ... }
    }
    

    Each object is composed of all sorts of tokens (arrays, strings, other objects, etc.), so it is dynamic JSON. (I used ellipses as placeholders rather than muddying up this question wit lots of JSON data.)

    I want to process "Object1", "Object2", and "Object3" separately using LINQ, however. So, ideally, I would like something like this:

    // these lines DO NOT work    
    var jsonObject1 = jsonWork.Children()["Object1"]
    var jsonObject2 = jsonWork.Children()["Object2"]
    var jsonObject3 = jsonWork.Children()["Object3"]
    

    But the above lines fail.

    I used var above because I have no idea what object type I should be using: JContainer, JObject, or JToken! Just so you know what I want to do, once the above jsonObject# variables are properly assigned, I would like to use LINQ to query the JSON they contain. Here is a very simple example:

    var query = from p in jsonObject1
       where p.Name == "Name1"
       select p
    

    Of course, my LINQ ultimately will filter for JSON arrays, objects, strings, etc., in the jsonObject variable. I think once I get going, I can use LinqPad to help me filter the JSON using LINQ.

    I discovered that if I use:

    // this line WORKS 
    var jsonObject1 = ((JObject)jsonWork).["Object1"];
    

    Then I get an JObject type in jsonObject1. Is this the correct approach?

    It is unclear to me when/why one would use JContainer when it seems that JToken and JObject objects work with LINQ quite well. What is the purpose of JContainer?

  • Jazimov
    Jazimov almost 8 years
    In your first example, also using (JToken)JsonConvert.DeserializeObject("[1, 2, 3]") works. I see from other answer that JContainer can hold everything a JToken can, including other JTokens. Is that the only difference? Your second example works, thanks!
  • Eli Arbel
    Eli Arbel almost 8 years
    JToken is the base class for all JSON elements. You should just use the Parse method for the type of element you expect to have in the string. If you don't know what it is, use JToken, and then you'll be able to down cast it to JObject, JArray, etc. In this case you always expect a JObject, so use that. Using JContainer is only ever useful if you want to enumerate the child items without caring what the parent is.
  • Chopnut
    Chopnut over 6 years
    This answer is most informative, I have looked everywhere how they all relates, and bam! Thank you for this, really appreciate a thorough tid bits.
  • Hugo Nava Kopp
    Hugo Nava Kopp almost 6 years
    Very nice. this is great documentation. Thanks bro
  • Brent Rittenhouse
    Brent Rittenhouse almost 6 years
    Yeah, this was great. Thanks!
  • johnny 5
    johnny 5 over 5 years
    @EliArbel, whats o in this example?
  • Dario Emerson
    Dario Emerson over 5 years
    Printed and put this in my office. Thanks bro!
  • Yaseer Arafat
    Yaseer Arafat almost 5 years
    It's a great documentation. Thanks a lot.