Skip to Content
Course content

68: JSON Serialization with System.Text.Json

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Most of the time, when we're talking about JSON in C#, we're just trying to get an object out of our memory and into a string so we can send it over a network or save it to a file. For years, everyone used Newtonsoft.Json, but System.Text.Json is now the built-in standard. It's faster and more memory-efficient, though it's a bit more strict. Let's see how it actually behaves.

Let's see what happens by default

I've got a simple GameCharacter class here. I want to take this character and turn it into a JSON string. I'll start with the most basic approach possible: JsonSerializer.Serialize.

using System.Text.Json;

public class GameCharacter
{
    public string Name { get; set; }
    public int Level { get; set; }
    public List<string> Inventory { get; set; }
}

var hero = new GameCharacter 
{ 
    Name = "Valerius", 
    Level = 12, 
    Inventory = new List<string> { "Iron Sword", "Health Potion", "Old Map" } 
};

string jsonString = JsonSerializer.Serialize(hero);
Console.WriteLine(jsonString);
// Output: {"Name":"Valerius","Level":12,"Inventory":["Iron Sword","Health Potion","Old Map"]}

It worked, but look at that output. It's a single, cramped line, and the property names start with capital letters. While this is valid JSON, most web APIs expect camelCase (lowercase first letter), and if I'm debugging this in a text editor, I'd much rather it be formatted nicely.

Wait, this looks like C#, not JSON

To fix the casing and the readability, I can't just pass the object into the serializer. I need to pass an instance of JsonSerializerOptions. This is where the actual configuration happens.

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

string prettyJson = JsonSerializer.Serialize(hero, options);
Console.WriteLine(prettyJson);
/* 
Output:
{
  "name": "Valerius",
  "level": 12,
  "inventory": [
    "Iron Sword",
    "Health Potion",
    "Old Map"
  ]
}
*/

That's much better. By setting PropertyNamingPolicy to CamelCase, the serializer automatically maps our C# Name property to "name" in the JSON string. WriteIndented just adds the whitespace and line breaks. It's a small change, but it makes a huge difference when you're staring at a log file for three hours.

Hiding the messy bits

Now, here is a real-world problem. Let's say my GameCharacter class has an InternalDbId. This is a GUID we use for database lookups, but we absolutely do not want to send that to the client or save it in a public-facing save file. It's internal noise.

public class GameCharacter
{
    [JsonIgnore] 
    public Guid InternalDbId { get; set; }
    public string Name { get; set; }
    public int Level { get; set; }
}

I just added the [JsonIgnore] attribute. When I run the serialization again, InternalDbId simply vanishes from the output. It's the cleanest way to handle "private" data that still needs to be a public property for other parts of your C# code to work.

Bringing the data back to life

Serialization is great, but it's useless if we can't get the object back. This is where JsonSerializer.Deserialize<T> comes in. Let's take a JSON string—maybe one we read from a file—and turn it back into a GameCharacter object.

string rawJson = "{\"name\":\"Valerius\",\"level\":12}";

// IMPORTANT: We must use the same options we used to serialize!
var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

GameCharacter restoredHero = JsonSerializer.Deserialize<GameCharacter>(rawJson, options);
Console.WriteLine($"Welcome back, {restoredHero.Name}!");

One thing to watch out for: if you forget the JsonSerializerOptions during deserialization, C# will look for a property named "name" (lowercase) in your class. Since your class has Name (uppercase), it won't find a match and will just leave the property as null or 0. It won't throw an error; it'll just fail silently. That's a classic bug that's a pain to track down, so always keep your options consistent.




📋 Practical Task

Exercise: Building a Save-Game System for a Space RPG

You are tasked with creating a save/load system for a Space RPG. You need to implement a system that saves a ShipState object to a JSON string and then loads it back.

Requirements:

  • Create a ShipState class with the following properties:
    • ShipName (string)
    • FuelLevel (double)
    • CrewMembers (List of strings)
    • SecretEncryptionKey (string) — This should not be included in the JSON output.
  • Write a method SaveShip(ShipState ship) that returns a JSON string. The JSON must be indented and use camelCase naming.
  • Write a method LoadShip(string json) that takes a JSON string and returns a ShipState object.
  • In your Main method, create a ship, save it to a string, print that string to the console to verify the formatting and the missing secret key, and then deserialize it back into a new object to verify the data persisted.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.