CodeGym /Courses /C# SELF /Dynamic structures: JObjec...

Dynamic structures: JObject, JArray

C# SELF
Level 47 , Lesson 3
Available

1. Introduction

In previous lectures we always worked with strongly-typed structures. For example, we have a class Person that we serialize to JSON and back:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

But sometimes you don't know the data structure in advance. For example:

  • You're writing a parser for an external service where the response structure isn't fixed.
  • You only need to extract part of the information, not necessarily fill a whole class.
  • You need to edit or generate JSON on the fly based on dynamic conditions.

That's where "dynamic structures" come in — objects that hold a JSON tree as a set of keys and values without requiring a pre-defined C# class.

Why Newtonsoft.Json is used most of the time

In .NET there are two main players when working with JSON:

  • System.Text.Json — the built-in Microsoft library (has evolved since .NET Core 3.0).
  • Newtonsoft.Json (Json.NET) — a popular library that gave C# classes like JObject and JArray.

At the time of writing System.Text.Json still doesn't offer a full analogue of JObject/JArray with the same convenience. So if you often need to parse or modify complex/unknown JSON structures, people usually pick Newtonsoft.Json.

Main types: JObject, JArray, JValue and the JToken family

  • JToken — the base type for all JSON nodes.
  • JObject — a JSON object { ... }, a set of key-value pairs.
  • JArray — an array [ ... ].
  • JValue — a single value (42, "text", true, null).

The idea is simple: parse JSON — get a "tree" of these tokens and then walk it, search, change, delete and add elements.

2. Reading unknown JSON

Suppose we received this JSON and we don't want or can't write a class for it in advance:

{
  "status": "ok",
  "amount": 150.5,
  "items": [
    {
      "name": "book",
      "qty": 1
    },
    {
      "name": "pen",
      "qty": 3
    }
  ]
}

With Newtonsoft.Json we can turn it into a tree and inspect it on the fly.

Example: reading JSON into a JObject

using Newtonsoft.Json.Linq;

string json = @"{
  ""status"": ""ok"",
  ""amount"": 150.5,
  ""items"": [
    { ""name"": ""book"", ""qty"": 1 },
    { ""name"": ""pen"", ""qty"": 3 }
  ]
}";

// Parse the string and get the tree
JObject root = JObject.Parse(json);

// Take properties like from a dictionary
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5

// items is an array, so JArray
JArray items = (JArray)root["items"];

// Iterate the array
foreach (JObject item in items)
{
    string name = (string)item["name"];
    int qty = (int)item["qty"];
    Console.WriteLine($"Product: {name}, Qty: {qty}");
}

Super convenient: no class declarations — you can quickly dig out the parts you need.

3. Indexers and dynamic access

Indexers:

  • For an object: root["status"], root["items"]
  • For an array: items[0], items[1]

To get the value directly in the desired type, use casting (string), (int), (bool), (double) — the library will convert the type automatically.

If the data might be missing, be careful: accessing a missing key returns null, and an explicit cast will throw. It's better to use checking methods:

if (root.TryGetValue("amount", out var token))
{
    double amount = token.Value<double>();
    // This is more convenient: Value<T>() converts immediately
}

Nested objects and arrays are also read simply:

// Get the second product
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"

You can also use dynamic:

dynamic droot = root;
Console.WriteLine(droot.status); // "ok"

But remember: with dynamic the compiler won't check field access — errors will surface only at runtime.

4. Modifying the JSON tree on the fly

Adding elements

root["currency"] = "RUB";        // Added a new property
items.Add(new JObject
{
    ["name"] = "eraser",
    ["qty"] = 2
});

Modifying and removing

root["status"] = "done";         // Changed the value
items[0]["qty"] = 5;             // Increased quantity of the first product
items.RemoveAt(1);               // Removed the second product

Final saving to a string

string modifiedJson = root.ToString();
// Or root.ToString(Formatting.Indented) for pretty output

5. Creating a JSON structure from scratch

var person = new JObject
{
    ["name"] = "Alice",
    ["age"] = 22,
    ["languages"] = new JArray { "C#", "Python" },
    ["isStudent"] = true
};

Console.WriteLine(person.ToString(Newtonsoft.Json.Formatting.Indented));
{
  "name": "Alice",
  "age": 22,
  "languages": [
    "C#",
    "Python"
  ],
  "isStudent": true
}

Useful when you need to return only part of the data to an external API or build JSON conditionally.

6. How to build your own object from a JObject

Sometimes you need to turn flexible JSON into a strict C# object. Options:

  • Regular deserialization: JsonConvert.DeserializeObject<MyClass>(...).
  • Manual object construction, extracting needed fields from a JObject.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Suppose we received this JSON:
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";

JObject j = JObject.Parse(incoming);

// Build the object manually — only needed fields
var person = new Person
{
    Name = (string)j["name"],
    Age = (int)j["age"],
    // extraField we don't need — and that's fine!
};

7. Examples of mistakes and nuances: typical traps

Working with dynamic JSON structures is flexible, but there are "gotchas":

  • Accessing a non-existent key/element yields null; explicit casting to a value type will throw an exception.
  • Mismatched expected type (expecting an object but a value arrives) — casting error.
  • To iterate object fields use Properties():
foreach (var prop in root.Properties())
{
    Console.WriteLine($"Field: {prop.Name}, Value: {prop.Value}");
}
  • Newtonsoft.Json works nicely with LINQ-like queries (filtering, searching):
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
    Console.WriteLine(item);

8. Common mistakes when working with JObject/JArray

Mistake #1: missing expected field or type mismatch. Very often a developer assumes an object will contain a certain field, but it's either missing or of a different type. If you expect an object but a number comes, casting will throw. Check existence and type before using.

Mistake #2: accessing fields of nested structures without null checks. When JSON has nested objects and keys differ or are absent, trying to access a missing field can crash. Check for null before reading nested node values.

Mistake #3: casting to a value type when the value is null. If the key exists but its value is null, an expression like (int)j["age"] will throw. Use Value<T>() — it will return the default value (for int that's 0, for strings — null).

Mistake #4: overusing dynamic objects instead of clear models. Very complex structures are often safer and easier to manage with C# classes: this reduces bugs and improves readability. Use dynamics where the structure is truly unknown or highly variable.

Now you know how to quickly and safely parse, modify and create JSON structures using JObject and JArray, even without pre-defined models.

2
Task
C# SELF, level 47, lesson 3
Locked
On-the-fly JSON Generation
On-the-fly JSON Generation
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION