cshttp Tutorial - Day 12: Sending Structured Data (JSON)

Back to cshttp Tutorial Main Menu

Day 12: Sending Structured Data (JSON)

In Day 11, our C# server and JavaScript passed one single block of plain text back and forth (text/plain). That worked great for a single notepad!

However, what if our C# server wants to send a structured object to JavaScript—for example, a Student Profile that has a name, age, exam score, membership status, join date, and a list of hobbies?

If we sent all of that as one plain text string, JavaScript would have to guess where the name ends, where the score begins, and whether "true" is text or a boolean!

To send structured data between a server and a browser, the entire web uses a universal text format called JSON (JavaScript Object Notation).

Today, we will:

  1. Learn what JSON looks like and explore every data type JSON supports (strings, dates, numbers, booleans, arrays, and null).
  2. Write structured, indented JSON in C# using a multiline $@"..." string (with "" double-quote escaping).
  3. Protect JSON strings from breaking when text contains quotes (") using WebApp.JavaScriptStringEncode().
  4. Serve our JSON from an API route (/api/student) with Response.ContentType = "application/json".
  5. Read the JSON in JavaScript on our Home page (/) using fetch('/api/student') and response.json()!

1. What Is JSON? (Structured Text with 6 Data Types)

Even though JSON sounds technical, here is the secret: JSON is still just plain text! You could type it in Notepad.

What makes JSON special is its strict, predictable shape that both C# and JavaScript understand instantly.

Let's look at a nicely indented JSON object representing a Student Profile:

{
    "name": "Alice",
    "age": 12,
    "score": 95.5,
    "isMember": true,
    "joinDate": "2026-10-27 14:30:00",
    "nickname": null,
    "hobbies": [
        "drawing",
        "football"
    ]
}

Look at the Rules and Data Types Inside This JSON:

  • Curly braces { ... } wrap the entire Object.
  • Inside { ... }, data is written as "key": value pairs, separated by commas (,). Notice that every key name must be wrapped in double quotes ("name", "age").
  • Look at the different kinds of values on the right side of the colon (:):
JSON KeyJSON ValueData TypeFormatting Rule in JSON
"name""Alice"String (Text)Always wrapped in double quotes ("...").
"age"12Whole Number (int)Written directly with no quotes.
"score"95.5Decimal Number (double)Written directly with no quotes.
"isMember"trueBoolean (bool)Always lowercase true or false (no quotes!).
"joinDate""2026-10-27 14:30:00"Date / TimeJSON has no special date symbol, so dates are written as strings inside double quotes ("...").
"nickname"nullNull (Empty / None)Always lowercase null (no quotes!).
"hobbies"["drawing", "football"]Array (List)Wrapped in square brackets [ ... ], with commas between items.

2. Writing Indented Multiline JSON in C# ($@"...")

How do we write that exact indented JSON string inside our C# code?

Remember from Day 2 that when we use a multiline verbatim string (@"..." or $@"...") in C#, the string itself starts and ends with a double quote (").

Because JSON requires real double quotes around every key ("name") and every string value ("Alice"), how do we write a double quote inside a C# $@"..." string without ending the string early?

C# Rule — Doubling Quotes ("") Inside $@"...": Inside a @"..." or $@"..." string, typing two double quotes right next to each other ("") tells C# to output one single literal double quote (")!

And one more rule we saw in Day 10:

C# Rule — Doubling Curly Braces ({{ and }}) Inside $@"...": Because $@"..." uses single {variable} braces to inject C# variables, we write {{ at the very top of our JSON and }} at the very bottom so C# outputs literal { and } characters for our JSON object!

Let's see how clean and structured this looks in C#:

string name = "Alice";
int age = 12;
double score = 95.5;
bool isMember = true;
string joinDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

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

Notice Two Important Details Above:

  1. Quotes vs. No Quotes:
    • ""{name}"" and ""{joinDate}"" have "" around them because strings and dates must be inside quotes in JSON.
    • {age} and {score} have no quotes around them because numbers in JSON are never quoted!
  2. Why {(isMember ? "true" : "false")} for Booleans?

    In C#, if you print a bool variable directly ({isMember}), C# capitalizes the first letter (True or False). However, JSON strictly requires lowercase true or false! Using {(isMember ? "true" : "false")} (or isMember.ToString().ToLower()) ensures it outputs lowercase true or false.


3. Keeping JSON Strings Safe with WebApp.JavaScriptStringEncode()

Wait—what happens if name (or a bio/message string) contains a double quote (") inside it?

For example, suppose a student's status message is:

I love "C#" programming!

If we plug that raw string directly into ""status"": ""{status}"", the resulting JSON becomes:

"status": "I love "C#" programming!"

Look closely at the quote right before C#: JSON thinks that quote is the end of the string, and your entire JSON breaks with a syntax error!

Just like we used WebApp.HtmlEncode() in Day 6 to make text safe for HTML, cshttp provides WebApp.JavaScriptStringEncode(text) to make text safe for JSON!

string rawStatus = @"I love ""C#"" programming!";
string safeStatus = WebApp.JavaScriptStringEncode(rawStatus);

WebApp.JavaScriptStringEncode() automatically escapes any " inside the text into \" (and escapes backslashes \ into \\ and line breaks into \n), so the JSON string is guaranteed to never break!


4. Creating the /api/student Route (HandleStudentApi)

Let's create our HandleStudentApi() method that builds our structured, indented JSON using $@"..." and sends it to the browser with the label Response.ContentType = "application/json";:

        static void HandleStudentApi()
        {
            string name = WebApp.JavaScriptStringEncode("Alice");
            string status = WebApp.JavaScriptStringEncode(@"Learning ""cshttp"" & JSON!");
            int age = 12;
            double score = 95.5;
            bool isMember = true;
            string joinDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

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

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

When you visit http://localhost:8080/api/student in your browser, your server sends back:

{
    "name": "Alice",
    "status": "Learning \"cshttp\" & JSON!",
    "age": 12,
    "score": 95.5,
    "isMember": true,
    "joinDate": "2026-10-27 18:55:00",
    "nickname": null,
    "hobbies": [
        "drawing",
        "football"
    ]
}

5. Reading JSON in JavaScript (response.json())

Now let's build HandleHome() so our Home page (/) fetches /api/student using JavaScript's fetch() and displays each property!

In Day 11, we used response.text() to read plain text.

Today, because /api/student returns JSON, we call response.json() instead!

fetch('/api/student')
    .then(function (response) {
        return response.json(); // <-- Automatically parses the JSON text into a real JavaScript object!
    })
    .then(function (student) {
        // Now we can access every property directly by name!
        document.getElementById('studentName').textContent = student.name;
        document.getElementById('studentStatus').textContent = student.status;
        document.getElementById('studentAge').textContent = student.age;
        document.getElementById('studentScore').textContent = student.score;
        document.getElementById('studentMember').textContent = student.isMember ? 'Yes (Active Member)' : 'No';
        document.getElementById('studentDate').textContent = student.joinDate;
        document.getElementById('studentNickname').textContent = student.nickname === null ? '(None / null)' : student.nickname;
        document.getElementById('studentHobbies').textContent = student.hobbies.join(', ');
    });

Look how powerful response.json() is:

  • student.name gives you "Alice".
  • student.age gives you the number 12.
  • student.isMember gives you a real boolean (true), so we can use student.isMember ? 'Yes' : 'No'!
  • student.nickname is a real JavaScript null.
  • student.hobbies is a real JavaScript array (["drawing", "football"]), so .join(', ') turns it into "drawing, football"!

6. Complete Code for Day 12

Here is the complete, clean Program.cs for Day 12, keeping only /home, /api/student, and HandleNotFound():

using System;
using cshttp;

namespace ConsoleApp1
{
    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;
                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'>View Raw /api/student JSON</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 12: Sends structured, indented JSON with strings, dates, numbers, booleans, null & array!
        static void HandleStudentApi()
        {
            string name = WebApp.JavaScriptStringEncode("Alice");
            string status = WebApp.JavaScriptStringEncode(@"Learning ""cshttp"" & JSON!");
            int age = 12;
            double score = 95.5;
            bool isMember = true;
            string joinDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

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

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

        // NEW IN DAY 12: Fetches /api/student and reads each JSON property using response.json()
        static void HandleHome()
        {
            string content = @"
                <h1>Student Profile (Loaded via JSON)</h1>
                <p>Click the button below to fetch structured JSON data from <code>/api/student</code>:</p>

                <button onclick='loadStudent()'>Load / Refresh Student JSON</button>
                <br /><br />

                <div style='background: #f9f9f9; border: 1px solid #ddd; padding: 15px; max-width: 450px;'>
                    <p><b>Name (String):</b> <span id='studentName'>-</span></p>
                    <p><b>Status (Escaped String):</b> <span id='studentStatus'>-</span></p>
                    <p><b>Age (Integer Number):</b> <span id='studentAge'>-</span></p>
                    <p><b>Score (Decimal Number):</b> <span id='studentScore'>-</span></p>
                    <p><b>Is Member (Boolean):</b> <span id='studentMember'>-</span></p>
                    <p><b>Join Date (Date String):</b> <span id='studentDate'>-</span></p>
                    <p><b>Nickname (Null):</b> <span id='studentNickname'>-</span></p>
                    <p><b>Hobbies (Array):</b> <span id='studentHobbies'>-</span></p>
                </div>

                <script>
                    function loadStudent() {
                        fetch('/api/student')
                            .then(function (response) {
                                return response.json(); // Parse the JSON text into a JavaScript object!
                            })
                            .then(function (student) {
                                document.getElementById('studentName').textContent = student.name;
                                document.getElementById('studentStatus').textContent = student.status;
                                document.getElementById('studentAge').textContent = student.age;
                                document.getElementById('studentScore').textContent = student.score;
                                document.getElementById('studentMember').textContent = student.isMember ? 'Yes (Active Member)' : 'No';
                                document.getElementById('studentDate').textContent = student.joinDate;
                                document.getElementById('studentNickname').textContent = student.nickname === null ? '(None / null)' : student.nickname;
                                document.getElementById('studentHobbies').textContent = student.hobbies.join(', ');
                            });
                    }

                    // Load automatically when the page opens
                    loadStudent();
                </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. First, click View Raw /api/student JSON in the navigation bar (or visit http://localhost:8080/api/student).
    • Because we formatted our $@"..." string with clean indentation and set Response.ContentType = "application/json", you can clearly see every JSON data type ("Alice", 12, 95.5, true, null, and ["drawing", "football"])!
    • Notice how "status" shows "Learning \"cshttp\" & JSON!"—WebApp.JavaScriptStringEncode() escaped the inner quotes automatically!
  3. Now go back to http://localhost:8080/.
    • All 8 fields of the Student Profile Card are populated automatically by loadStudent()!
    • Click Load / Refresh Student JSON a few times—watch the seconds in Join Date update live without reloading the page!

8. Quick Comparison: response.text() vs. response.json()

FeatureDay 11 (Plain Text)Day 12 (Structured JSON)
C# Response.ContentType"text/plain""application/json"
What C# sendsOne flat string ("Buy milk...")Structured { "key": value } text
How JavaScript reads itresponse.text()response.json()
What JavaScript receivesA single stringA real JavaScript object (student.name, student.score, student.isMember)

In Day 13, we will learn how a popular C# library called Newtonsoft.Json can automatically convert any C# class or List<T> into formatted JSON in one single line of code—so you don't even have to write the "" quotes or { } braces by hand!