Tutorials / Build a REST API with C# and .NET · Lesson 9 of 15

On this page

    JSON

    Back in the HTTP lesson we saw that an API sends its answer as text in the body of the response, and that the text is usually JSON. We used it without explaining it.

    This lesson has two parts:

    1. What JSON is, and why almost every web API uses it. This is short.
    2. A cookbook: how the C# things you already write (a class, a number, a date, a list) look when they become JSON. You will not memorise it. You will come back to it when you build the API and want to know "what does this look like on the wire?"

    What JSON is

    JSON stands for JavaScript Object Notation. It is a plain-text way to write down data, so that one program can hand the data to another program.

    Here is a book, written in JSON:

    {
      "title": "Dune",
      "price": 12.5,
      "inStock": true
    }
    

    It is only text. You can read it, and so can a computer. That is the whole idea, and it is the most important thing to know about JSON.

    Readable by people and by programs

    Most data formats are good for one side. A binary file is easy for a computer and impossible for a person. A page of prose is easy for a person and useless to a program. JSON is very good for both.

    JSON works for people and programs A developer reads it, debugging { "title": "Dune", "price": 12.5 } A program parses it, one call the same text, no translation needed
    • For people: you can open a response, read it, and see what is in it straight away. The names say what each value is ("title", "price"), and the layout is clean and short. There are no tags and no extra noise.
    • For programs: the rules are few and strict, so a library can read it in one call and get exactly the same result every time.

    This matters a lot in daily work. When something goes wrong, the first question is "what did the API actually send?" With JSON you open the response in Swagger or the browser's network tab and see the answer with your own eyes, with no special tool to decode it. A format that is quick to read is a format that is quick to debug.

    Why did JSON become the standard?

    The format came from JavaScript. Its shape is copied from how JavaScript writes an object, and a developer called Douglas Crockford named it and pushed it in the early 2000s. Three things made it spread.

    • JavaScript reads it natively. Every browser can turn JSON text into a real JavaScript object with one call. The web runs on JavaScript, so this mattered a lot.
    • It is light and easy to read. Before JSON, web services mostly used XML, which wraps every value in opening and closing tags. The same book in XML is longer and harder to scan. JSON is shorter and cleaner.
    • Every language can read and write it. JSON is only text, so it does not belong to JavaScript. C#, Python, Java, Go and phone apps all have a library for it. The program that sends the JSON does not need to know which language is reading it.
    One format, many readers Your API written in C# JSON plain text JavaScript page Phone app Python script the sender does not care who reads it

    Two small honest notes:

    • JSON is lighter than XML, not the lightest possible. Some binary formats are smaller and faster, but you cannot read them with your eyes. For web APIs, being readable and universal won.
    • JSON looks like JavaScript, but it is stricter. We will see the exact rules next.

    The building blocks

    JSON has only a few pieces. Everything you will ever see is made from these.

    The building blocks of JSON Object { "name": value } named things like a C# class Array [ value, value ] a list, in order like a C# List Value "text" 12.5 true false null
    • An object is wrapped in { } and holds named values: "title": "Dune". The name is called the key.
    • An array is wrapped in [ ] and holds values in order, separated by commas.
    • A value is text in double quotes, a number, true or false, null, or another object or array. Objects and arrays can hold each other, so you can nest as deep as you like.

    And the rules that catch people out, usually when they copy from JavaScript:

    • Keys and text use double quotes only. 'Dune' is not valid.
    • No trailing comma after the last item.
    • No comments.
    • There is no date type, and no undefined.

    Two words you will hear

    Your C# code holds data as objects in memory. JSON is text. Moving between the two has names:

    • Serialize: turn a C# object into JSON text. The API does this before it sends a response.
    • Deserialize: turn JSON text back into a C# object. The API does this when a request arrives with JSON in its body.
    Objects to text and back C# object in the API JSON text sent over HTTP JS object in the browser serialize JSON.parse JSON.stringify deserialize solid lines: the API sends data. Dashed lines: the browser sends data.

    In .NET you will not write this conversion yourself. When an endpoint returns a C# object, ASP.NET Core serializes it for you, and when a request arrives with JSON, it deserializes it for you. What you need is to know what comes out, so you can predict what the frontend will receive. That is what the cookbook is for.


    Cookbook: C# things as JSON

    To see real output, the examples below use System.Text.Json, the JSON library built into .NET. ASP.NET Core uses the same library, with the same settings as the Web option below:

    using System.Text.Json;
    
    var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true };
    
    string json = JsonSerializer.Serialize(book, options);
    

    Serialize is the C# object to JSON text step. WriteIndented only adds line breaks so we can read it. A real API sends it without them to save space.

    We start with a small Book and give it one new property at a time. After each step you see the new part of the JSON.

    A class becomes an object

    public class Book
    {
        public string Title { get; set; } = "";
        public decimal Price { get; set; }
        public bool InStock { get; set; }
    }
    

    Now make one book and give each property a value:

    var book = new Book
    {
        Title = "Dune",
        Price = 12.5m,
        InStock = true
    };
    

    Serialized, it becomes:

    {
      "title": "Dune",
      "price": 12.5,
      "inStock": true
    }
    

    Each public property becomes one key and its value. Two things to notice:

    • The names change case. C# writes Title and InStock. With the web settings, JSON gets title and inStock. This style is called camelCase: first letter small, then a capital at each new word. JavaScript developers expect it. If you serialize without the Web option, you get Title and InStock, which is why it is worth knowing about it.
    • Only public properties are included. Private members and plain public fields (without { get; set; }) are skipped.

    Text, numbers and booleans

    C# JSON Example
    string text in double quotes "Dune"
    int, long number 3
    decimal, double number 12.5
    bool true or false (lowercase) true

    One warning about money. JSON has a single "number" type, and JavaScript reads every number as a double, which can be slightly off for some decimal values. A price like 12.5 is fine. For real money calculations, do the maths on the server in C# and treat the JSON number as a value to display.

    Nothing: null

    public string? Subtitle { get; set; }
    
    Subtitle = null
    
    "subtitle": null
    

    A property with no value becomes null. Notice that the key is still there. This also works the other way: if the incoming JSON has no subtitle at all, the C# property keeps its default, which is null for a string.

    Ids and dates become text

    JSON has no type for these, so they are written as text in a standard format.

    public Guid Id { get; set; }
    public DateTime Published { get; set; }
    
    Id = Guid.Parse("3f2a9c1e-5b7d-4e2a-9a10-6d8c4f1b2e77"),
    Published = new DateTime(2026, 10, 20, 9, 30, 0)
    
    "id": "3f2a9c1e-5b7d-4e2a-9a10-6d8c4f1b2e77",
    "published": "2026-10-20T09:30:00"
    
    • A Guid becomes text.
    • A DateTime becomes text in the ISO 8601 format: year, month, day, then T, then the time. This is the format everybody agrees on, and JavaScript can read it directly.

    An enum becomes a number

    public enum Category { Fiction, Cooking, Travel }
    
    public Category Category { get; set; }
    
    Category = Category.Fiction
    
    "category": 0
    

    Here is what each value of the enum becomes:

    C# JSON
    Category = Category.Fiction "category": 0
    Category = Category.Cooking "category": 1
    Category = Category.Travel "category": 2

    By default an enum is written as its number, here 0 for Fiction. That surprises many people, because a number tells the frontend nothing. There is a setting that writes the name instead ("Fiction"). We will not use it yet. For now remember that the default is a number, and that the numbers are decided by the order in the enum.

    A class inside a class becomes an object inside an object

    public class Author
    {
        public string Name { get; set; } = "";
        public string Country { get; set; } = "";
    }
    
    public Author? Author { get; set; }
    
    Author = new Author { Name = "Frank Herbert", Country = "USA" }
    
    "author": {
      "name": "Frank Herbert",
      "country": "USA"
    }
    

    The inner class follows the same rules, including the camelCase names. You can nest as deep as you like.

    A list becomes an array

    public List<string> Tags { get; set; } = new();
    
    Tags = new List<string> { "classic", "sci-fi" }
    
    "tags": [
      "classic",
      "sci-fi"
    ]
    

    List<T>, arrays and most collections become a JSON array, in the same order. An empty list becomes [], not null.

    A list of objects is an array of objects. This is what an endpoint like "get all books" returns:

    var books = new List<Book>
    {
        new() { Title = "Dune", Price = 12.5m, InStock = true },
        new() { Title = "Emma", Price = 8m, InStock = false }
    };
    
    [
      { "title": "Dune", "price": 12.5, "inStock": true },
      { "title": "Emma", "price": 8, "inStock": false }
    ]
    

    (Here the objects are written on one line each to save space. The data is the same.)

    Notice the difference between one book and many: one book is { ... }, many books is [ { ... }, { ... } ]. The frontend code must expect the right one.

    A dictionary becomes an object

    A Dictionary<string, int> is a set of names with values, for example the number of copies per category. JSON has just the thing for it, an object:

    var stock = new Dictionary<string, int> { ["Fiction"] = 120, ["Cooking"] = 45 };
    
    {
      "Fiction": 120,
      "Cooking": 45
    }
    

    The keys are written exactly as they are in the dictionary. The camelCase rule applies to property names, not to dictionary keys.

    The whole Book

    Put everything together and one Book looks like this:

    {
      "id": "3f2a9c1e-5b7d-4e2a-9a10-6d8c4f1b2e77",
      "title": "Dune",
      "subtitle": null,
      "price": 12.5,
      "inStock": true,
      "published": "2026-10-20T09:30:00",
      "category": 0,
      "author": {
        "name": "Frank Herbert",
        "country": "USA"
      },
      "tags": [
        "classic",
        "sci-fi"
      ]
    }
    

    All the output in this section was produced by running the code with .NET 10. Your Guid and dates will of course differ.

    The other direction: JSON to a C# object

    Deserializing is the same recipe in reverse. The key names match the property names, and the case does not matter with the Web options:

    string json = """{ "title": "Emma", "price": 8, "inStock": false }""";
    
    Book book = JsonSerializer.Deserialize<Book>(json, options)!;
    
    Console.WriteLine($"{book.Title} {book.Price} {book.InStock}");
    
    Emma 8 False
    

    What happens when the JSON does not match the class:

    • A key the class does not have (for example "colour": "red") is ignored.
    • A property the JSON does not have keeps its default: 0 for a number, false for a bool, null or "" for text.
    • Broken JSON, such as a trailing comma, throws a JsonException:
    The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Path: $ | LineNumber: 0 | BytePositionInLine: 16.
    

    In a real API you will not see this exception. ASP.NET Core catches it and answers with a 400 Bad Request for you. We will look at how in the errors lesson.

    The same thing in JavaScript

    You will meet the other end of the wire in the frontend lessons. For now, here are the two calls that match Serialize and Deserialize:

    const book = JSON.parse('{ "title": "Dune", "price": 12.5 }');
    console.log(book.title); // Dune
    
    const text = JSON.stringify(book);
    console.log(text); // {"title":"Dune","price":12.5}
    

    JSON.parse turns JSON text into a JavaScript object. JSON.stringify turns a JavaScript object into JSON text. Because the C# side sends camelCase names, the JavaScript side can write book.title, which reads naturally.


    Quick lookup

    C# JSON
    a class (Book) object { }
    a public property Title a key "title"
    string "text"
    int, decimal, double 12.5
    bool true / false
    null null
    Guid "3f2a..." (text)
    DateTime "2026-10-20T09:30:00" (text)
    enum a number (by default)
    a class inside a class an object inside an object
    List<T>, array array [ ]
    Dictionary<string, T> object { }

    Recap

    • JSON is plain text for data. It came from JavaScript, and it won because browsers read it natively, it is short and readable, and every language can handle it.
    • It is built from objects { }, arrays [ ] and simple values. Double quotes only, no trailing commas, no comments, no date type.
    • Serialize means C# object to JSON text. Deserialize means the reverse. ASP.NET Core does both for you.
    • A class becomes an object, a list becomes an array, a dictionary becomes an object, and ids and dates become text.
    • Property names become camelCase, and an enum is a number by default.

    Questions to think about

    Pick one answer for each. The answers are not given here on purpose, so check yourself against the lesson.

    1. A C# property is named InStock. With the web settings, what is its key in the JSON?

      • A. InStock
      • B. inStock
      • C. in_stock
      • D. instock
    2. Which of these is valid JSON?

      • A. { 'title': 'Dune' }
      • B. { title: "Dune" }
      • C. { "title": "Dune", }
      • D. { "title": "Dune" }
    3. An endpoint returns List<Book> and the shop has 3 books. What does the frontend receive?

      • A. One object with three keys
      • B. An array with three objects
      • C. Three separate JSON texts
      • D. An object that holds a number, 3
    4. A Book has public DateTime Published { get; set; }. What does it look like in the JSON?

      • A. A number of seconds
      • B. A special date value
      • C. Text such as "2026-10-20T09:30:00"
      • D. An object with year, month and day
    5. Why is JSON a good choice for an API whose clients may be a web page today and a phone app next year?

      • A. It only works with JavaScript, so everything stays the same
      • B. It is plain text that every language can read and write
      • C. It is the smallest and fastest format that exists
      • D. It is encrypted, so it is safe to send
    6. A Category enum has Fiction, Cooking, Travel, and a book's category is Travel. With the default settings, what is written?

      • A. "Travel"
      • B. "travel"
      • C. 2
      • D. 3
    ← Service Lifetimes: ScopedAsynchronous Code: async and await →