cshttp Tutorial - Day 12: Sending Structured Data (JSON)
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:
- Learn what JSON looks like and explore every data type JSON supports (strings, dates, numbers, booleans, arrays, and
null). - Write structured, indented JSON in C# using a multiline
$@"..."string (with""double-quote escaping). - Protect JSON strings from breaking when text contains quotes (
") usingWebApp.JavaScriptStringEncode(). - Serve our JSON from an API route (
/api/student) withResponse.ContentType = "application/json". - Read the JSON in JavaScript on our Home page (
/) usingfetch('/api/student')andresponse.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": valuepairs, 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 Key | JSON Value | Data Type | Formatting Rule in JSON |
|---|---|---|---|
"name" | "Alice" | String (Text) | Always wrapped in double quotes ("..."). |
"age" | 12 | Whole Number (int) | Written directly with no quotes. |
"score" | 95.5 | Decimal Number (double) | Written directly with no quotes. |
"isMember" | true | Boolean (bool) | Always lowercase true or false (no quotes!). |
"joinDate" | "2026-10-27 14:30:00" | Date / Time | JSON has no special date symbol, so dates are written as strings inside double quotes ("..."). |
"nickname" | null | Null (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:
- 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!
- Why
{(isMember ? "true" : "false")}for Booleans?In C#, if you print a
boolvariable directly ({isMember}), C# capitalizes the first letter (TrueorFalse). However, JSON strictly requires lowercasetrueorfalse! Using{(isMember ? "true" : "false")}(orisMember.ToString().ToLower()) ensures it outputs lowercasetrueorfalse.
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.namegives you"Alice".student.agegives you the number12.student.isMembergives you a real boolean (true), so we can usestudent.isMember ? 'Yes' : 'No'!student.nicknameis a real JavaScriptnull.student.hobbiesis 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
- Run the application (
dotnet runor press Start in Visual Studio) and visithttp://localhost:8080/. - 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 setResponse.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!
- Because we formatted our
- 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!
- All 8 fields of the Student Profile Card are populated automatically by
8. Quick Comparison: response.text() vs. response.json()
| Feature | Day 11 (Plain Text) | Day 12 (Structured JSON) |
|---|---|---|
C# Response.ContentType | "text/plain" | "application/json" |
| What C# sends | One flat string ("Buy milk...") | Structured { "key": value } text |
| How JavaScript reads it | response.text() | response.json() |
| What JavaScript receives | A single string | A 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!