CodeGym /Corsi /C# SELF /Strutture dinamiche: JObje...

Strutture dinamiche: JObject, JArray

C# SELF
Livello 47 , Lezione 3
Disponibile

1. Introduzione

Nelle lezioni precedenti abbiamo sempre lavorato con strutture fortemente tipizzate. Per esempio, abbiamo una classe Person che serializziamo in JSON e back:

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

Ma a volte non conosci la struttura dei dati a priori. Per esempio:

  • Stai scrivendo un parser per un servizio esterno dove la struttura della risposta non è fissa.
  • Ti serve estrarre solo una parte delle informazioni, non è necessario popolare una classe intera.
  • Devi modificare o generare JSON "al volo" basandoti su condizioni dinamiche.

Qui entrano in gioco le "strutture dinamiche" — oggetti che tengono l'albero JSON come coppie chiave-valore senza richiedere una classe C# descritta in anticipo.

Perché spesso si usa Newtonsoft.Json

In .NET ci sono due principali player per lavorare con JSON:

  • System.Text.Json — la libreria incorporata di Microsoft (si è evoluta da .NET Core 3.0).
  • Newtonsoft.Json (Json.NET) — libreria popolare che ha introdotto in C# classi come JObject e JArray.

Al momento della stesura di questa lezione System.Text.Json non offre ancora un equivalente completo di JObject/JArray con la stessa praticità. Quindi, se devi spesso analizzare o modificare strutture JSON complesse/indeterminate, spesso si sceglie Newtonsoft.Json.

Tipi principali: JObject, JArray, JValue e la famiglia JToken

  • JToken — tipo base per tutti i nodi JSON.
  • JObject — oggetto JSON { ... }, insieme di coppie "chiave-valore".
  • JArray — array [ ... ].
  • JValue — valore singolo (42, "testo", true, null).

L'idea è semplice: parsare il JSON — ottieni un "albero" di questi token e puoi navigarlo, cercare, cambiare, eliminare e aggiungere elementi.

2. Lettura di JSON sconosciuto

Metti che arrivi questo JSON e non vuoi o non puoi scrivere una classe apposita:

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

Con Newtonsoft.Json possiamo trasformarlo in un albero e ispezionarlo al volo.

Esempio: leggere JSON in JObject

using Newtonsoft.Json.Linq;

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

// Parsiamo la stringa e otteniamo l'albero
JObject root = JObject.Parse(json);

// Prendiamo le proprietà come da dizionario
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5

// items è un array, quindi JArray
JArray items = (JArray)root["items"];

// Iteriamo l'array
foreach (JObject item in items)
{
    string name = (string)item["name"];
    int qty = (int)item["qty"];
    Console.WriteLine($"Prodotto: {name}, Quantità: {qty}");
}

Molto comodo: niente dichiarazione di classi — puoi estrarre velocemente i pezzi che ti servono.

3. Indicizzatori e accesso dinamico

Indicizzatori:

  • Per l'oggetto: root["status"], root["items"]
  • Per l'array: items[0], items[1]

Per ottenere il valore subito nel tipo voluto, usa il cast (string), (int), (bool), (double) — la libreria converte automaticamente il tipo.

Se i dati possono mancare, fai attenzione: l'accesso a una chiave assente restituirà null, e un cast esplicito fallirà. Meglio usare metodi con controllo:

if (root.TryGetValue("amount", out var token))
{
    double amount = token.Value<double>();
    // Questo è comodo: Value<T>() converte direttamente
}

Oggetti e array annidati si leggono allo stesso modo:

// Prendere il secondo prodotto
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"

Si può anche usare dinamico:

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

Ma ricorda: con dynamic il compilatore non controllerà l'accesso ai campi — gli errori emergono solo a runtime.

4. Modificare l'albero JSON al volo

Aggiungere elementi

root["currency"] = "RUB";        // Aggiunta di una nuova proprietà
items.Add(new JObject
{
    ["name"] = "eraser",
    ["qty"] = 2
});

Modificare e rimuovere

root["status"] = "done";         // Modificato valore
items[0]["qty"] = 5;             // Incrementata la quantità del primo prodotto
items.RemoveAt(1);               // Rimosso il secondo prodotto

Salvataggio finale in stringa

string modifiedJson = root.ToString();
// Oppure root.ToString(Formatting.Indented) per formattazione

5. Creare una struttura JSON da zero

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
}

Utile se devi restituire a un API esterno solo una parte dei dati o costruire JSON in base a condizioni.

6. Come costruire il proprio oggetto da JObject

A volte devi convertire un JSON flessibile in un oggetto C# rigido. Opzioni:

  • Deserializzazione normale: JsonConvert.DeserializeObject<MyClass>(...).
  • Costruire manualmente l'oggetto estraendo i campi necessari da JObject.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Supponiamo arrivi questo JSON:
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";

JObject j = JObject.Parse(incoming);

// Costruiamo l'oggetto manualmente — solo i campi che ci servono
var person = new Person
{
    Name = (string)j["name"],
    Age = (int)j["age"],
    // extraField non ci serve — perfetto!
};

7. Esempi di errori e dettagli: trappole tipiche

Lavorare con strutture JSON dinamiche è flessibile, ma ci sono alcune "insidie":

  • L'accesso a una chiave/elemento inesistente dà null; un cast esplicito a tipo valore lancerà un'eccezione.
  • Mancata corrispondenza del tipo atteso (ti aspetti un oggetto ma arriva un valore) — errore di cast.
  • Per iterare i campi di un oggetto usa Properties():
foreach (var prop in root.Properties())
{
    Console.WriteLine($"Campo: {prop.Name}, Valore: {prop.Value}");
}
  • Con Newtonsoft.Json è comodo lavorare con query in stile LINQ (filtri, ricerche):
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
    Console.WriteLine(item);

8. Errori tipici quando si lavora con JObject/JArray

Errore n.1: assenza del campo previsto o mismatch di tipo. Spesso lo sviluppatore si aspetta che un oggetto contenga un campo specifico, ma quel campo non c'è o è di tipo diverso. Se ti aspetti un oggetto e arriva un numero, il cast lancerà un'eccezione. Controlla presenza e tipo prima di usare.

Errore n.2: accesso a campi di strutture annidate senza check per null. Quando nel JSON ci sono oggetti annidati e le chiavi differiscono o mancano, provare ad accedere a un campo assente può far fallire l'applicazione. Controlla null prima di leggere i valori dei nodi figli.

Errore n.3: cast a tipo valore quando il valore è null. Se la chiave esiste ma il suo valore è null, espressioni tipo (int)j["age"] lanceranno un'eccezione. Usa Value<T>() — restituirà il valore di default (per int è 0, per le stringhe è null).

Errore n.4: abuso degli oggetti dinamici invece di modelli chiari. Strutture troppo complesse è meglio descriverle con classi C#: riduce gli errori e migliora la leggibilità del codice. Usa dinamica solo quando la struttura è veramente sconosciuta o molto variabile.

Ora sai come parsare, modificare e creare velocemente strutture JSON con JObject e JArray, anche senza modelli predefiniti.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION