CodeGym /Cursos /C# SELF /Estructuras dinámicas: JOb...

Estructuras dinámicas: JObject, JArray

C# SELF
Nivel 47, Lección 3
Disponible

1. Introducción

En las lecciones anteriores siempre trabajamos con estructuras fuertemente tipadas. Por ejemplo, tenemos la clase Person, que serializamos a JSON y de regreso:

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

Pero a veces no conoces la estructura de los datos de antemano. Por ejemplo:

  • Estás escribiendo un parser para un servicio externo donde la estructura de la respuesta no es fija.
  • Necesitas extraer solo parte de la información, no necesariamente rellenar toda una clase.
  • Hay que editar o generar JSON "al vuelo" basándose en condiciones dinámicas.

Ahí entran en juego las "estructuras dinámicas": objetos que guardan el árbol JSON como un conjunto de claves y valores, sin requerir una clase C# descrita de antemano.

Por qué casi siempre se usa Newtonsoft.Json

En .NET hay dos jugadores principales para trabajar con JSON:

  • System.Text.Json — la librería integrada de Microsoft (evoluciona desde .NET Core 3.0).
  • Newtonsoft.Json (Json.NET) — librería popular que dio a C# clases como JObject y JArray.

En el momento de escribir la lección System.Text.Json todavía no ofrece un equivalente completo de JObject/JArray con la misma comodidad. Por eso, si necesitas frecuentemente parsear o modificar estructuras JSON complejas/desconocidas, la elección frecuente es Newtonsoft.Json.

Tipos principales: JObject, JArray, JValue y la familia JToken

  • JToken — tipo base para todos los nodos JSON.
  • JObject — objeto JSON { ... }, conjunto de pares "clave-valor".
  • JArray — array [ ... ].
  • JValue — valor individual (42, "texto", true, null).

La idea es simple: parseas JSON — obtienes un "árbol" de estos tokens y puedes recorrerlo, buscar, cambiar, borrar y añadir elementos.

2. Leer JSON desconocido

Supongamos que llega este JSON y no queremos o no podemos definir una clase para él de antemano:

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

Con Newtonsoft.Json podemos convertirlo en un árbol y explorarlo al vuelo.

Ejemplo: leer JSON en un JObject

using Newtonsoft.Json.Linq;

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

// Parseamos la cadena y obtenemos el árbol
JObject root = JObject.Parse(json);

// Tomamos propiedades como de un diccionario
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5

// items es un array, así que JArray
JArray items = (JArray)root["items"];

// Iteramos el array
foreach (JObject item in items)
{
    string name = (string)item["name"];
    int qty = (int)item["qty"];
    Console.WriteLine($"Producto: {name}, Cantidad: {qty}");
}

Muy conveniente: sin declarar clases — puedes rascar rápidamente los trozos que necesitas.

3. Indexadores y acceso dinámico

Indexadores:

  • Para objeto: root["status"], root["items"]
  • Para array: items[0], items[1]

Para obtener el valor directamente en el tipo deseado, usa cast (string), (int), (bool), (double) — la librería convierte el tipo automáticamente.

Si los datos pueden no existir, ten cuidado: acceder a una clave ausente devolverá null, y el cast explícito fallará. Mejor usar métodos con comprobación:

if (root.TryGetValue("amount", out var token))
{
    double amount = token.Value<double>();
    // Esta forma es más cómoda: Value<T>() – convierte directamente
}

Los objetos y arrays anidados también se leen fácilmente:

// Obtener el segundo producto
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"

También puedes hacerlo dinámicamente:

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

Pero recuerda: con dynamic el compilador no comprobará el acceso a campos — los errores saldrán solo en tiempo de ejecución.

4. Modificar el árbol JSON al vuelo

Añadir elementos

root["currency"] = "RUB";        // Añadimos una nueva propiedad
items.Add(new JObject
{
    ["name"] = "eraser",
    ["qty"] = 2
});

Modificar y eliminar

root["status"] = "done";         // Cambiamos el valor
items[0]["qty"] = 5;             // Aumentamos la cantidad del primer producto
items.RemoveAt(1);               // Eliminamos el segundo producto

Guardar el resultado en una cadena

string modifiedJson = root.ToString();
// O root.ToString(Formatting.Indented) para que quede bonito

5. Crear una estructura JSON desde cero

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
}

Útil si necesitas devolver a una API externa solo parte de los datos o construir JSON según condiciones.

6. Cómo poblar tu propio objeto desde un JObject

A veces hay que convertir JSON flexible a un objeto C# estricto. Opciones:

  • Deserialización normal: JsonConvert.DeserializeObject<MyClass>(...).
  • Construcción manual del objeto, extrayendo los campos necesarios desde JObject.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Supongamos que llegó este JSON:
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";

JObject j = JObject.Parse(incoming);

// Construimos el objeto manualmente — solo los campos que necesitamos
var person = new Person
{
    Name = (string)j["name"],
    Age = (int)j["age"],
    // extraField no nos interesa — ¡perfecto!
};

7. Ejemplos de errores y matices: trampas típicas

Trabajar con JSON dinámico es flexible, pero hay "piedras bajo el agua":

  • Acceder a una clave/elemento inexistente devuelve null; el cast explícito a un tipo valorará lanzar una excepción.
  • Desajuste de tipo esperado (esperas un objeto, pero llega un valor) — error de cast.
  • Para iterar los campos de un objeto usa Properties():
foreach (var prop in root.Properties())
{
    Console.WriteLine($"Campo: {prop.Name}, Valor: {prop.Value}");
}
  • Con Newtonsoft.Json es cómodo usar consultas tipo LINQ (filtrado, búsqueda):
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
    Console.WriteLine(item);

8. Errores típicos al trabajar con JObject/JArray

Error nº1: ausencia del campo esperado o desajuste de tipo. Muy a menudo el desarrollador cuenta con que el objeto tendrá cierto campo, pero no existe o es de otro tipo. Si se espera un objeto y llega un número, al castear habrá una excepción. Comprueba existencia y tipo antes de usar.

Error nº2: acceder a campos de estructuras anidadas sin comprobar null. Cuando el JSON tiene objetos anidados y las claves difieren o faltan, intentar acceder a un campo ausente puede provocar un fallo. Haz comprobación de null antes de leer valores de nodos anidados.

Error nº3: castear a un tipo valor cuando el valor es null. Si la clave existe pero su valor es null, una expresión como (int)j["age"] lanzará una excepción. Usa Value<T>() — devolverá el valor por defecto (para int es 0, para strings — null).

Error nº4: abuso de objetos dinámicos en lugar de modelos claros. Estructuras demasiado complejas es mejor y más seguro describirlas con clases C#: reduce errores y mejora la legibilidad. Usa dinámica solo donde la estructura sea realmente desconocida o muy variable.

Ahora sabes cómo parsear, modificar y crear estructuras JSON rápidamente y de forma segura con JObject y JArray, incluso sin modelos descritos de antemano.

2
Tarea
C# SELF, nivel 47, lección 3
Bloqueada
Generación de JSON "al vuelo"
Generación de JSON "al vuelo"
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION