La lliçó anterior va desar el catàleg de BiblioTech en un fitxer de text pla amb un parseig manual, camp a camp, separat per ;. Funciona, però és fràgil: un títol amb un ; trencaria el format, i cada nova estructura de dades (préstecs, socis amb més propietats...) exigiria inventar i mantenir a mà un format de text diferent. La serialització resol això de forma general: converteix qualsevol objecte en una representació reutilitzable (avui dia, gairebé sempre JSON) sense que el programador hagi de dissenyar ni parsejar cap format propi. Aquesta lliçó presenta System.Text.Json, la biblioteca estàndard de .NET per treballar amb JSON, i substitueix el parseig manual de la lliçó anterior per serialització real de Biblioteca.Cataleg i Biblioteca.Socis.

Contingut

  1. Què és la serialització i quin problema resol
  2. Una ullada al passat: per què el JSON va guanyar al binari i a l'XML
  3. System.Text.Json: serialitzar amb JsonSerializer.Serialize
  4. Deserialitzar amb JsonSerializer.Deserialize
  5. Personalitzant el JSON: JsonSerializerOptions i [JsonPropertyName]
  6. El repte de l'herència: serialitzar un catàleg amb Llibre i Revista
  7. Persistint Biblioteca completa en JSON

  1. Què és la serialització i quin problema resol

Serialitzar un objecte és convertir el seu estat (el valor de les seves propietats) en una seqüència de dades que es pot desar en un fitxer, enviar per xarxa, o emmagatzemar en una base de dades. Deserialitzar és l'operació inversa: reconstruir un objecte a partir d'aquesta representació. És exactament el que la lliçó anterior feia a mà amb ; com a separador, només que ara es delega en una biblioteca que ja resol, de forma genèrica, casos que un parseig manual gestiona malament: text amb caràcters especials, llistes imbricades, valors absents...

flowchart LR
    A[Objecte en memoria] -->|Serialitzar| B[Text / JSON]
    B -->|Deserialitzar| C[Objecte reconstruit]

  1. Una ullada al passat: per què el JSON va guanyar al binari i a l'XML

.NET ha tingut, històricament, diversos mecanismes de serialització:

Format Llegible per humans Estat actual
Binari (BinaryFormatter) No Obsolet i retirat per motius de seguretat greus (permetia executar codi arbitrari en deserialitzar dades no confiables); no s'ha d'usar en codi nou
XML (XmlSerializer, DataContractSerializer) Sí, però verbós Continua existint, usat sobretot en sistemes heretats o formats que ja l'exigeixen (com alguns serveis SOAP)
JSON (System.Text.Json, abans Newtonsoft.Json) Sí, i compacte Estàndard de facto avui en dia

El JSON s'ha imposat com a format per defecte per diverses raons pràctiques: és més compacte que l'XML, és llegible directament per una persona (a diferència del binari), és el format natiu de JavaScript i per tant de pràcticament tota API web moderna, i System.Text.Json (inclòs a .NET des de la versió 3.0, sense necessitat d'instal·lar cap paquet addicional) ofereix un rendiment molt superior a les alternatives històriques. Per això aquesta lliçó —i l'última del mòdul, sobre APIs REST— se centren exclusivament en JSON.

  1. System.Text.Json: serialitzar amb JsonSerializer.Serialize

El punt d'entrada per serialitzar qualsevol objecte és el mètode estàtic JsonSerializer.Serialize, de l'espai de noms System.Text.Json:

using System.Text.Json;

Soci soci = new Soci(1, "Anna Martinez");
string json = JsonSerializer.Serialize(soci);

Console.WriteLine(json);
// {"Id":1,"Nom":"Anna Martinez"}

JsonSerializer.Serialize recorre, mitjançant reflexió (una tècnica que veuràs en detall al Mòdul 6), totes les propietats públiques de l'objecte i produeix un text JSON equivalent. No cal escriure cap codi de conversió manual: n'hi ha prou que les propietats siguin públiques, com ja ho són a Soci, Llibre i la resta del model de BiblioTech.

També funciona directament sobre col·leccions completes:

List<Soci> socis = new List<Soci>
{
    new Soci(1, "Anna Martinez"),
    new Soci(2, "Lluis Gomez")
};

string jsonSocis = JsonSerializer.Serialize(socis);
Console.WriteLine(jsonSocis);
// [{"Id":1,"Nom":"Anna Martinez"},{"Id":2,"Nom":"Lluis Gomez"}]

  1. Deserialitzar amb JsonSerializer.Deserialize

L'operació inversa, JsonSerializer.Deserialize<T>, reconstrueix un objecte (o una col·lecció) a partir d'un text JSON, indicant el tipus de destinació com a argument de tipus genèric (recorda els genèrics del Mòdul 4):

string json = "{\"Id\":1,\"Nom\":\"Anna Martinez\"}";

Soci? soci = JsonSerializer.Deserialize<Soci>(json);
Console.WriteLine(soci?.Nom); // "Anna Martinez"

string jsonSocis = "[{\"Id\":1,\"Nom\":\"Anna Martinez\"},{\"Id\":2,\"Nom\":\"Lluis Gomez\"}]";
List<Soci>? socisDeserialitzats = JsonSerializer.Deserialize<List<Soci>>(jsonSocis);
Console.WriteLine(socisDeserialitzats?.Count); // 2

Deserialize<T> retorna T? (pot retornar null si el JSON d'entrada és literalment la cadena "null"); a la pràctica, si el JSON prové d'un fitxer que el teu propi programa va generar, el resultat mai serà null, però el compilador t'obliga a tenir-ho en compte, igual que amb FirstOrDefault a la lliçó de LINQ.

Combinat amb el que has après a la lliçó anterior, un objecte es desa i es recupera del disc en només dues línies:

File.WriteAllText("soci.json", JsonSerializer.Serialize(soci));
Soci? sociRecuperat = JsonSerializer.Deserialize<Soci>(File.ReadAllText("soci.json"));

  1. Personalitzant el JSON: JsonSerializerOptions i [JsonPropertyName]

Per defecte, JsonSerializer fa servir el nom exacte de cada propietat de C# com a clau JSON ("Nom", amb majúscula inicial). Dos mecanismes permeten personalitzar això:

  • JsonSerializerOptions: un objecte de configuració que es passa com a segon argument, amb opcions com WriteIndented (formata el JSON amb salts de línia i sagnat, molt més llegible per inspeccionar un fitxer a mà).
  • [JsonPropertyName("clau")]: un atribut (els atributs s'estudien en profunditat al Mòdul 6) que es col·loca sobre una propietat per indicar quina clau JSON fer servir en el seu lloc, útil quan el JSON ha de seguir una convenció diferent de PascalCase (per exemple, el snake_case habitual en moltes APIs).
using System.Text.Json;
using System.Text.Json.Serialization;

class SociJson
{
    public int Id { get; set; }

    [JsonPropertyName("nom_complet")]
    public string Nom { get; set; } = string.Empty;
}

var opcions = new JsonSerializerOptions { WriteIndented = true };

SociJson soci = new SociJson { Id = 1, Nom = "Anna Martinez" };
string json = JsonSerializer.Serialize(soci, opcions);

Console.WriteLine(json);
// {
//   "Id": 1,
//   "nom_complet": "Anna Martinez"
// }

Per a la resta d'aquesta lliçó, Soci es serialitza sense [JsonPropertyName] (les claus JSON coincideixen amb les propietats de C# tal qual), però convé conèixer l'atribut perquè reapareixerà a l'última lliçó del mòdul, en consumir una API REST externa el JSON de la qual no segueix, gairebé amb tota seguretat, les convencions de noms de C#.

  1. El repte de l'herència: serialitzar un catàleg amb Llibre i Revista

Biblioteca.Cataleg és un List<MaterialBibliotecari> que en realitat conté una barreja de Llibre i Revista (polimorfisme, Mòdul 3). Serialitzar aquesta llista tal qual, sense res més, produeix un problema: en deserialitzar, JsonSerializer no sap si cada element del JSON s'ha de reconstruir com a Llibre o com a RevistaMaterialBibliotecari és abstract i no es pot instanciar directament.

Des de .NET 7, System.Text.Json resol això amb serialització polimòrfica: s'anota la classe base amb [JsonPolymorphic] i [JsonDerivedType] per a cada tipus derivat, indicant un valor de "discriminador" que es desa junt amb cada objecte i permet reconstruir el tipus correcte en deserialitzar:

using System.Text.Json.Serialization;

[JsonPolymorphic(TypeDiscriminatorPropertyName = "tipus")]
[JsonDerivedType(typeof(Llibre), "llibre")]
[JsonDerivedType(typeof(Revista), "revista")]
abstract class MaterialBibliotecari : IPrestable, ICercable
{
    // ... Titol, Autor, Disponible, constructor, Prestar(), Retornar(), MostrarFitxa(),
    //     Descriure(), Coincideix() sense canvis ...
}

Amb aquests atributs afegits, serialitzar i deserialitzar List<MaterialBibliotecari> funciona de forma transparent, incloent automàticament el tipus concret de cada element:

List<MaterialBibliotecari> cataleg = new List<MaterialBibliotecari>
{
    new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"),
    new Revista("National Geographic", "Varios autores", 302)
};

var opcions = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(cataleg, opcions);
Console.WriteLine(json);
// [
//   { "tipus": "llibre", "Isbn": "978-84-376-0495-4", "Titol": "Rayuela", ... },
//   { "tipus": "revista", "NumeroEdicio": 302, "Titol": "National Geographic", ... }
// ]

List<MaterialBibliotecari>? catalegRecuperat =
    JsonSerializer.Deserialize<List<MaterialBibliotecari>>(json, opcions);

Console.WriteLine(catalegRecuperat?[0] is Llibre);   // True
Console.WriteLine(catalegRecuperat?[1] is Revista);  // True

Sense [JsonPolymorphic]/[JsonDerivedType], JsonSerializer.Deserialize<List<MaterialBibliotecari>> llançaria una excepció, perquè no hi hauria manera de saber quin tipus concret instanciar per a cada element de l'array JSON. Aquest mateix repte —com persistir un model amb herència— reapareixerà, resolt d'una altra manera, quan arribis a Entity Framework més endavant en aquest mòdul.

  1. Persistint Biblioteca completa en JSON

Amb tot l'anterior, Biblioteca reemplaça els mètodes de text pla de la lliçó anterior per una versió basada en JSON, que desa Cataleg i Socis en un únic fitxer mitjançant una petita classe auxiliar que agrupa totes dues col·leccions:

class EstatBiblioteca
{
    public List<MaterialBibliotecari> Cataleg { get; set; } = new List<MaterialBibliotecari>();
    public List<Soci> Socis { get; set; } = new List<Soci>();
}

class Biblioteca
{
    // ... Cataleg, Socis, Prestecs, _socisPerId, PrestecRegistrat sense canvis ...
    // ... AfegirMaterial, AfegirSoci, CercarSociPerId, RegistrarPrestec, PrestarLlibreAsync sense canvis ...

    private static readonly JsonSerializerOptions OpcionsJson = new JsonSerializerOptions
    {
        WriteIndented = true
    };

    public void DesarEstatJson(string ruta)
    {
        EstatBiblioteca estat = new EstatBiblioteca
        {
            Cataleg = Cataleg,
            Socis = Socis
        };

        string json = JsonSerializer.Serialize(estat, OpcionsJson);
        File.WriteAllText(ruta, json);
    }

    public void CarregarEstatJson(string ruta)
    {
        if (!File.Exists(ruta))
        {
            throw new FileNotFoundException($"No s'ha trobat el fitxer d'estat: {ruta}");
        }

        string json = File.ReadAllText(ruta);
        EstatBiblioteca? estat = JsonSerializer.Deserialize<EstatBiblioteca>(json, OpcionsJson);

        if (estat is null)
        {
            return;
        }

        Cataleg.Clear();
        foreach (MaterialBibliotecari material in estat.Cataleg)
        {
            AfegirMaterial(material);
        }

        Socis.Clear();
        foreach (Soci soci in estat.Socis)
        {
            AfegirSoci(soci);
        }
    }
}

EstatBiblioteca és una classe d'usar i llençar, exclusivament per a aquest propòsit: agrupa Cataleg i Socis en un únic objecte perquè JsonSerializer produeixi un sol fitxer amb totes dues col·leccions, en lloc de dos fitxers separats. DesarEstatJson i CarregarEstatJson substitueixen directament DesarCatalegText/CarregarCatalegText de la lliçó anterior, amb l'avantatge afegit que ara també persisteixen Socis, cosa que el format de text pla no contemplava.

Errors Comuns i Consells

  • Serialitzar un tipus amb herència sense [JsonPolymorphic]/[JsonDerivedType]: la serialització d'un List<MaterialBibliotecari> "sembla" funcionar (produeix JSON vàlid), però la deserialització falla, perquè System.Text.Json no sap reconstruir un tipus abstract. Si el model té herència i necessites deserialitzar-lo de tornada als seus tipus concrets, aquests atributs no són opcionals.
  • Confondre Serialize/Deserialize amb validació de dades: la serialització no valida que les dades tinguin sentit de negoci (un Disponible que en realitat hauria de ser impossible, per exemple); només converteix l'estat de l'objecte d'un format a un altre. La validació continua sent responsabilitat de la mateixa classe (constructors, propietats amb set controlat, com a la lliçó d'Encapsulació).
  • Oblidar que Deserialize<T> pot retornar null: comprova sempre el resultat amb is null abans d'utilitzar-lo, igual que amb qualsevol altre tipus de referència anul·lable.
  • Fer servir BinaryFormatter perquè apareix en tutorials antics: està obsolet i suposa un risc de seguretat real en deserialitzar dades que no controles del tot; en codi nou, fes servir sempre System.Text.Json (o, si el projecte ja el feia servir abans, l'alternativa de tercers Newtonsoft.Json, avui dia també en desús davant la biblioteca estàndard).
  • Consell: activa WriteIndented = true mentre desenvolupes i depures (el JSON resultant és molt més fàcil d'inspeccionar a simple vista); considera desactivar-ho en fitxers de producció molt grans, on l'espai addicional del sagnat pot importar.

Exercicis

  1. Crea una classe Soci (la ja coneguda) i serialitza-la a JSON amb JsonSerializerOptions { WriteIndented = true }. Mostra el resultat per consola i comprova visualment que les claus fan servir els noms de les propietats de C#.

  2. Deserialitza el JSON "[{\"Id\":1,\"Nom\":\"Anna Martinez\"},{\"Id\":2,\"Nom\":\"Lluis Gomez\"}]" a un List<Soci> i mostra el Nom de cada soci recuperat amb un foreach.

  3. Afegeix [JsonPolymorphic]/[JsonDerivedType] a MaterialBibliotecari tal com s'ha mostrat en aquesta lliçó. Crea un List<MaterialBibliotecari> amb un Llibre i una Revista, serialitza'l, i deserialitza'l de tornada a un nou List<MaterialBibliotecari>; comprova amb is Llibre/is Revista que cada element va recuperar el seu tipus concret original.

Solucions

Soci soci = new Soci(1, "Anna Martinez");
var opcions = new JsonSerializerOptions { WriteIndented = true };

string json = JsonSerializer.Serialize(soci, opcions);
Console.WriteLine(json);
// {
//   "Id": 1,
//   "Nom": "Anna Martinez"
// }
string json = "[{\"Id\":1,\"Nom\":\"Anna Martinez\"},{\"Id\":2,\"Nom\":\"Lluis Gomez\"}]";
List<Soci>? socis = JsonSerializer.Deserialize<List<Soci>>(json);

if (socis is not null)
{
    foreach (Soci soci in socis)
    {
        Console.WriteLine(soci.Nom);
    }
}
List<MaterialBibliotecari> cataleg = new List<MaterialBibliotecari>
{
    new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"),
    new Revista("National Geographic", "Varios autores", 302)
};

var opcions = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(cataleg, opcions);

List<MaterialBibliotecari>? recuperat =
    JsonSerializer.Deserialize<List<MaterialBibliotecari>>(json, opcions);

Console.WriteLine(recuperat?[0] is Llibre);   // True
Console.WriteLine(recuperat?[1] is Revista);  // True

Conclusió

En aquesta lliçó has après què és la serialització, per què el JSON s'ha convertit en el format estàndard davant alternatives històriques com el binari o l'XML, i a fer servir System.Text.Json per serialitzar i deserialitzar objectes i col·leccions, incloent el cas més complex d'un model amb herència mitjançant [JsonPolymorphic]/[JsonDerivedType]. Biblioteca ja persisteix Cataleg i Socis complets en un fitxer JSON, amb DesarEstatJson i CarregarEstatJson, substituint definitivament el parseig manual de la lliçó anterior.

Desar l'estat sencer en un fitxer cada vegada, però, deixa de ser pràctic quan les dades creixen molt o quan diverses parts d'una aplicació necessiten llegir i escriure el mateix estat de forma concurrent i consistent: aquest és el terreny de les bases de dades relacionals. La lliçó següent, Connectivitat amb Bases de Dades, introdueix l'ADO.NET clàssic per desar el catàleg de BiblioTech en una base de dades SQLite real, posant els fonaments per a Entity Framework, la lliçó que la segueix.

Curs de Programació en C#

Mòdul 1: Introducció al C#

Mòdul 2: Estructures de Control

Mòdul 3: Programació Orientada a Objectes

Mòdul 4: Conceptes Avançats de C#

Mòdul 5: Treballant amb Dades

Mòdul 6: Temes Avançats

Mòdul 7: Construcció d'Aplicacions

Mòdul 8: Bones Pràctiques i Patrons de Disseny

Mòdul 9: Projecte Final

© Copyright 2026. Tots els drets reservats