cshttp Tutorial - Day 13: Let a Library Write the JSON (`Newtonsoft.Json`)

Back to cshttp Tutorial Main Menu

Day 13: Let a Library Write the JSON (Newtonsoft.Json)

In Day 12, we built our JSON string by hand inside HandleStudentApi():

string json = $@"{{
    ""name"": ""{name}"",
    ""status"": ""{status}"",
    ""age"": {age},
    ""score"": {score},
    ""isMember"": {(isMember ? "true" : "false")},
    ""joinDate"": ""{joinDate}"",
    ""nickname"": null,
    ""hobbies"": [
        ""drawing"",
        ""football""
    ]
}}";

Writing JSON by hand taught us that JSON is just structured text—there is no magic!

However, think about how many careful rules we had to remember when writing JSON by hand:

  • Doubling every quote (""name"") and curly brace ({{ and }}).
  • Remembering which types need quotes (string, DateTime) and which types must not have quotes (int, double, bool, null).
  • Converting bool to lowercase "true" / "false".
  • Calling WebApp.JavaScriptStringEncode() on every text field so a " inside the text doesn't break the JSON.

In real-world C# applications, we don't write JSON strings by hand. Instead, we use a JSON Library that automatically converts any C# class or List<T> into valid, safely-escaped JSON in one single line of code!

Today, we will use the most popular JSON library in the C# ecosystem: Newtonsoft.Json (also known as Json.NET).

In Day 13, we will:

  1. Install Newtonsoft.Json using NuGet (the package manager built into Visual Studio and .NET).
  2. Create a Student C# class with properties for every data type (string, int, double, bool, DateTime, List<string>, and null).
  3. Convert a Student object into indented JSON in 1 line using JsonConvert.SerializeObject(student, Formatting.Indented).
  4. Use [JsonProperty("...")] attributes so our C# properties can use PascalCase (Name) while our JSON keys use camelCase ("name").
  5. Convert a whole List<Student> into a JSON array and display it on our Home page (/) using fetch()!

1. Installing Newtonsoft.Json via NuGet

NuGet is like an official app store for C# libraries. You can install Newtonsoft.Json using either Visual Studio's graphical menu or a single terminal command:

Option A: Using Visual Studio (Graphical UI)

  1. In Solution Explorer, right-click on your project (or References) and click Manage NuGet Packages....
  2. Click the Browse tab at the top left.
  3. Type Newtonsoft.Json into the search box.
  4. Select Newtonsoft.Json (by James Newton-King) and click Install.

Option B: Using the Terminal (VS Code / Command Line)

Open a terminal in your project folder (where your .csproj file is) and run:

dotnet add package Newtonsoft.Json

Once installed, add using Newtonsoft.Json; at the very top of your Program.cs file:

using Newtonsoft.Json;

2. Creating the Student Class in C#

In Day 12, we represented our student using separate variables (name, status, age, score, isMember, joinDate, nickname, hobbies).

Let's group all of those fields into a clean C# class called Student:

    internal class Student
    {
        public string Name { get; set; }
        public string Status { get; set; }
        public int Age { get; set; }
        public double Score { get; set; }
        public bool IsMember { get; set; }
        public DateTime JoinDate { get; set; }
        public string Nickname { get; set; }
        public List<string> Hobbies { get; set; }
    }

Now, let's create a Student object in C# and fill in its properties:

Student student = new Student
{
    Name = "Alice",
    Status = @"Learning ""cshttp"" & Newtonsoft.Json!",
    Age = 12,
    Score = 95.5,
    IsMember = true,
    JoinDate = DateTime.Now,
    Nickname = null,
    Hobbies = new List<string> { "drawing", "football" }
};

3. One-Line Magic: JsonConvert.SerializeObject()

How do we turn that student object into a formatted, indented JSON string using Newtonsoft.Json?

Just one single line of code:

string json = JsonConvert.SerializeObject(student, Formatting.Indented);
  • SerializeObject means "turn this C# object into a JSON string".
  • Formatting.Indented tells Newtonsoft.Json to format the output with clean line breaks and indentation so humans can read it easily! (If you omit Formatting.Indented, it outputs compact JSON on a single line).

When JsonConvert.SerializeObject(student, Formatting.Indented) runs, here is what it produces automatically:

{
  "Name": "Alice",
  "Status": "Learning \"cshttp\" & Newtonsoft.Json!",
  "Age": 12,
  "Score": 95.5,
  "IsMember": true,
  "JoinDate": "2026-10-27T19:00:00.1234567+08:00",
  "Nickname": null,
  "Hobbies": [
    "drawing",
    "football"
  ]
}

Look at everything JsonConvert.SerializeObject() handled for us automatically:

  1. It wrapped strings in "..." and automatically escaped the inner quotes in Status (\"cshttp\")—no manual WebApp.JavaScriptStringEncode needed!
  2. It wrote numbers (12, 95.5), booleans (true), and null without quotes.
  3. It converted List<string> into a JSON array ["drawing", "football"].
  4. It formatted DateTime into the international ISO-8601 standard date string.

4. Solving the Case-Sensitivity Problem with [JsonProperty("...")]

Look closely at the JSON keys generated above:

  • "Name" (starts with an uppercase N)
  • "Status" (starts with an uppercase S)
  • "IsMember" (starts with an uppercase I)

Why? Because by default, Newtonsoft.Json copies the exact C# property name (public string Name -> "Name").

However, remember our JavaScript code from Day 12:

document.getElementById('studentName').textContent = student.name; // lowercase 'n'!

In JavaScript, property names are case-sensitive: student.name and student.Name are two completely different things!

  • In C#, the standard naming convention for properties is PascalCase (Name, IsMember, JoinDate).
  • In JavaScript & JSON, the standard naming convention is camelCase ("name", "isMember", "joinDate").

Furthermore, what if you later rename a C# property from Name to StudentFullName? If the JSON key is tied to the C# property name, renaming a C# variable would break your JavaScript code!

The Solution: [JsonProperty("...")]

Newtonsoft.Json gives us a special label (called an Attribute) that we can place right above each property in our C# class to specify the exact JSON key name we want:

    internal class Student
    {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("status")]
        public string Status { get; set; }

        [JsonProperty("age")]
        public int Age { get; set; }

        [JsonProperty("score")]
        public double Score { get; set; }

        [JsonProperty("isMember")]
        public bool IsMember { get; set; }

        [JsonProperty("joinDate")]
        public DateTime JoinDate { get; set; }

        [JsonProperty("nickname")]
        public string Nickname { get; set; }

        [JsonProperty("hobbies")]
        public List<string> Hobbies { get; set; }
    }

Now both sides get what they want:

  • Your C# code uses clean C# PascalCase (student.Name, student.IsMember).
  • Your JSON output uses clean JavaScript camelCase ("name", "isMember"), and never breaks even if you rename the C# property later!

5. Serializing a Whole List<Student> (HandleStudentsApi)

What if we have a list of multiple students (List<Student>)?

JsonConvert.SerializeObject() works on a List<Student> in one single line, too!

Let's add a second route (/api/students) that creates a List<Student> with two students (Alice and Bob) and serializes the entire list into a JSON array ([ { ... }, { ... } ]):

        static void HandleStudentsApi()
        {
            List<Student> students = new List<Student>
            {
                new Student
                {
                    Name = "Alice",
                    Status = @"Learning ""cshttp"" & Newtonsoft.Json!",
                    Age = 12,
                    Score = 95.5,
                    IsMember = true,
                    JoinDate = DateTime.Now,
                    Nickname = null,
                    Hobbies = new List<string> { "drawing", "football" }
                },
                new Student
                {
                    Name = "Bob",
                    Status = "Building web APIs in C#!",
                    Age = 14,
                    Score = 88.0,
                    IsMember = false,
                    JoinDate = DateTime.Now.AddDays(-7),
                    Nickname = "Bobby",
                    Hobbies = new List<string> { "coding", "chess" }
                }
            };

            string json = JsonConvert.SerializeObject(students, Formatting.Indented);

            Response.ContentType = "application/json";
            Response.Write(json);
        }

6. Complete Code for Day 13

Here is the complete, self-contained Program.cs for Day 13. Make sure you have installed the Newtonsoft.Json NuGet package before running!

using System;
using System.Collections.Generic;
using cshttp;
using Newtonsoft.Json; // NEW IN DAY 13: Import Newtonsoft.Json

namespace ConsoleApp1
{
    // NEW IN DAY 13: [JsonProperty("...")] maps C# PascalCase properties to JSON camelCase keys!
    internal class Student
    {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("status")]
        public string Status { get; set; }

        [JsonProperty("age")]
        public int Age { get; set; }

        [JsonProperty("score")]
        public double Score { get; set; }

        [JsonProperty("isMember")]
        public bool IsMember { get; set; }

        [JsonProperty("joinDate")]
        public DateTime JoinDate { get; set; }

        [JsonProperty("nickname")]
        public string Nickname { get; set; }

        [JsonProperty("hobbies")]
        public List<string> Hobbies { get; set; }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            WebApp.Run(8080, OnRequest);
        }

        static void OnRequest()
        {
            string path = Request.Path.ToLower();

            switch (path)
            {
                case "/":
                case "/home":
                    HandleHome();
                    break;
                case "/api/student":
                    HandleStudentApi();
                    break;
                case "/api/students":
                    HandleStudentsApi();
                    break;
                default:
                    HandleNotFound();
                    break;
            }
        }

        static void RenderPage(string title, string bodyContent)
        {
            string html = $@"<!DOCTYPE html>
<html>
    <head>
        <title>{title}</title>
    </head>
    <body>
        <nav>
            <a href='/'>Home</a> | 
            <a href='/api/student' target='_blank'>Raw /api/student (1 Object)</a> | 
            <a href='/api/students' target='_blank'>Raw /api/students (List)</a>
        </nav>
        <hr />

        {bodyContent}

        <hr />
        <footer>
            <small>Server Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}</small>
        </footer>
    </body>
</html>";

            Response.Write(html);
        }

        // NEW IN DAY 13: Serialize ONE Student object into indented JSON in one line!
        static void HandleStudentApi()
        {
            Student student = new Student
            {
                Name = "Alice",
                Status = @"Learning ""cshttp"" & Newtonsoft.Json!",
                Age = 12,
                Score = 95.5,
                IsMember = true,
                JoinDate = DateTime.Now,
                Nickname = null,
                Hobbies = new List<string> { "drawing", "football" }
            };

            string json = JsonConvert.SerializeObject(student, Formatting.Indented);

            Response.ContentType = "application/json";
            Response.Write(json);
        }

        // NEW IN DAY 13: Serialize a LIST of Student objects into a JSON array in one line!
        static void HandleStudentsApi()
        {
            List<Student> students = new List<Student>
            {
                new Student
                {
                    Name = "Alice",
                    Status = @"Learning ""cshttp"" & Newtonsoft.Json!",
                    Age = 12,
                    Score = 95.5,
                    IsMember = true,
                    JoinDate = DateTime.Now,
                    Nickname = null,
                    Hobbies = new List<string> { "drawing", "football" }
                },
                new Student
                {
                    Name = "Bob",
                    Status = "Building web APIs in C#!",
                    Age = 14,
                    Score = 88.0,
                    IsMember = false,
                    JoinDate = DateTime.Now.AddDays(-7),
                    Nickname = "Bobby",
                    Hobbies = new List<string> { "coding", "chess" }
                }
            };

            string json = JsonConvert.SerializeObject(students, Formatting.Indented);

            Response.ContentType = "application/json";
            Response.Write(json);
        }

        static void HandleHome()
        {
            string content = @"
                <h1>Newtonsoft.Json Demo</h1>
                <p>Click the button below to fetch <code>/api/students</code> (a C# <code>List&lt;Student&gt;</code> serialized with <code>JsonConvert.SerializeObject</code>):</p>

                <button onclick='loadStudents()'>Load Students List via JSON</button>
                <br /><br />

                <div id='studentList'></div>

                <script>
                    function loadStudents() {
                        fetch('/api/students')
                            .then(function (response) {
                                return response.json(); // Parses the JSON array into a JavaScript array of objects!
                            })
                            .then(function (students) {
                                var container = document.getElementById('studentList');
                                container.innerHTML = '';

                                for (var i = 0; i < students.length; i++) {
                                    var s = students[i];

                                    var card = document.createElement('div');
                                    card.style.background = '#f9f9f9';
                                    card.style.border = '1px solid #ddd';
                                    card.style.padding = '15px';
                                    card.style.marginBottom = '15px';
                                    card.style.maxWidth = '450px';

                                    var title = document.createElement('h3');
                                    title.style.marginTop = '0';
                                    title.textContent = s.name + ' (Age ' + s.age + ')';

                                    var details = document.createElement('p');
                                    details.textContent = 'Status: ' + s.status +
                                        ' | Score: ' + s.score +
                                        ' | Member: ' + (s.isMember ? 'Yes' : 'No') +
                                        ' | Nickname: ' + (s.nickname === null ? '(none)' : s.nickname) +
                                        ' | Hobbies: ' + s.hobbies.join(', ');

                                    var dateInfo = document.createElement('small');
                                    dateInfo.style.color = 'gray';
                                    dateInfo.textContent = 'Joined: ' + new Date(s.joinDate).toLocaleString();

                                    card.appendChild(title);
                                    card.appendChild(details);
                                    card.appendChild(dateInfo);
                                    container.appendChild(card);
                                }
                            });
                    }

                    // Load automatically when the page opens
                    loadStudents();
                </script>";

            RenderPage("Home", content);
        }

        static void HandleNotFound()
        {
            Response.StatusCode = 404;
            string content = @"
                <h1>404 Not Found</h1>
                <p>The page that you are looking for does not exist.</p>";

            RenderPage("404 Not Found", content);
        }
    }
}

7. Test It in Your Browser

  1. Run the application (dotnet run or press Start in Visual Studio) and visit http://localhost:8080/.
  2. Click Raw /api/student (1 Object) in the navigation bar:
    • You see the single Student object formatted with clean indentation, lowercase camelCase keys ("name", "status", "isMember"), and automatically escaped quotes (\"cshttp\")!
  3. Click Raw /api/students (List) in the navigation bar:
    • You see a JSON array ([ { ... }, { ... } ]) containing both Alice and Bob, generated by a single JsonConvert.SerializeObject(students, Formatting.Indented) call!
  4. Go back to http://localhost:8080/:
    • Both Alice and Bob are displayed in clean profile cards, with their ISO-8601 joinDate automatically formatted into a friendly local date and time by new Date(s.joinDate).toLocaleString()!

8. Quick Reference Summary

GoalC# Code
Install Newtonsoft.Jsondotnet add package Newtonsoft.Json (or via Visual Studio NuGet Manager)
Import the namespaceusing Newtonsoft.Json;
Object or List → Compact JSONstring json = JsonConvert.SerializeObject(obj);
Object or List → Indented JSONstring json = JsonConvert.SerializeObject(obj, Formatting.Indented);
JSON Text → C# Object (Deserialize)Student s = JsonConvert.DeserializeObject<Student>(jsonText);
Custom JSON key name for a property[JsonProperty("name")] placed right above the C# property

In Day 14, you will put everything you have learned from Day 1 to Day 13 together to build a complete To-Do List Web Application!