How to Convert Object to JSON Without Escape Characters in C#

Most people who search convert object to JSON without escape characters C# just ran JsonSerializer.Serialize on a name like Tom & Jerry’s and got this back:

{"Customer":"Tom \u0026 Jerry\u0027s \u003CCaf\u00E9\u003E","City":"Seattle, WA","Total":42.50}

Quick answer: set Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping in JsonSerializerOptions. Use it only for JSON sent as UTF-8, never inside an HTML page.

I tested every fix on .NET 10 with one Seattle bakery order, plus the double-serialized string bug and how Newtonsoft.Json differs.

Why Does JsonSerializer Escape Characters in C#?

Here’s the code behind that output. It’s a .NET 10 file-based app, the kind you can run from a console app in VS Code with dotnet run DefaultEscaping.cs:

#:property PublishAot=false
using System.Text.Json;

Console.OutputEncoding = System.Text.Encoding.UTF8;
var order = new Order("Tom & Jerry's <Café>", "Seattle, WA", 42.50m);

string json = JsonSerializer.Serialize(order);
Console.WriteLine(json);

record Order(string Customer, string City, decimal Total);

Output:

{"Customer":"Tom \u0026 Jerry\u0027s \u003CCaf\u00E9\u003E","City":"Seattle, WA","Total":42.50}
VS Code running DefaultEscaping.cs: System.Text.Json JsonSerializer.Serialize turns & ' < > and é into \u0026 \u0027 \u003C \u003E \u00E9
The default encoder escapes HTML-sensitive characters and every non-ASCII letter

Nothing is broken. The default encoder escapes HTML-sensitive characters (&, ', <, >, and " as \u0022) plus all non-ASCII text, so the JSON stays safe if someone drops it into a web page.

Any JSON parser turns \u0026 back into &, so the data round-trips fine. The escapes only matter when a person reads the JSON or a picky system compares raw text.

Note: the #:property PublishAot=false line matters. File-based apps turn on native AOT by default, which disables reflection-based JsonSerializer calls.

Serialize JSON Without Escape Characters: UnsafeRelaxedJsonEscaping

Same order, now with a cake note that contains real quotes. I swap in the relaxed encoder and turn on WriteIndented:

#:property PublishAot=false
using System.Text.Encodings.Web;
using System.Text.Json;

Console.OutputEncoding = System.Text.Encoding.UTF8;
var order = new Order("Tom & Jerry's <Café>", "Write \"Happy 30th\" on top", 42.50m);

var options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    WriteIndented = true
};
Console.WriteLine(JsonSerializer.Serialize(order, options));

record Order(string Customer, string Note, decimal Total);

Output:

{
  "Customer": "Tom & Jerry's <Café>",
  "Note": "Write \"Happy 30th\" on top",
  "Total": 42.50
}
VS Code running RelaxedEscaping.cs: JavaScriptEncoder.UnsafeRelaxedJsonEscaping with WriteIndented prints Tom & Jerry's <Café> without escape characters
UnsafeRelaxedJsonEscaping leaves & ' < > and é alone; the quotes inside Note still get \”

The name reads as typed. The \"Happy 30th\" escapes stay, because JSON itself requires a backslash before a quote inside a string.

Important: Microsoft’s docs say to use this encoder only when the client reads the payload as UTF-8 JSON, for example with Content-Type: application/json; charset=utf-8. Never write its raw output into an HTML page or a <script> block.

Keep Non-English JSON Text Readable with UnicodeRanges.All

If only the \u00E9 bothers you, there’s a safer middle ground. JavaScriptEncoder.Create(UnicodeRanges.All) lets every language through but keeps the HTML escapes:

#:property PublishAot=false
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

Console.OutputEncoding = System.Text.Encoding.UTF8;
var order = new Order("Tom & Jerry's <Café>", "Pastel de cumpleaños, 寿司");

var options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};
Console.WriteLine(JsonSerializer.Serialize(order, options));

record Order(string Customer, string Item);

Output:

{"Customer":"Tom \u0026 Jerry\u0027s \u003CCafé\u003E","Item":"Pastel de cumpleaños, 寿司"}
VS Code running UnicodeRangesAll.cs: JavaScriptEncoder.Create(UnicodeRanges.All) keeps Café, cumpleaños and Japanese text readable while & ' < > stay escaped
UnicodeRanges.All: accents and Japanese come through, HTML-sensitive characters stay escaped

Café, cumpleaños and 寿司 print as-is, while & and < are still escaped. I pick this for JSON that might end up in a page. UTF-8 stores é as two bytes, as shown when you convert a string to a byte array in C#.

How Do You Fix Double-Serialized JSON (\u0022 Everywhere)?

The other classic: an API already returns a JSON string, and the code serializes it again. Now the whole object is one quoted string:

#:property PublishAot=false
using System.Text.Json;

var order = new Order("Ballard Bakery", 42.50m);
string json = JsonSerializer.Serialize(order);

string twice = JsonSerializer.Serialize(json);     // the mistake
Console.WriteLine(twice);

// the fix: keep the JSON as a JsonElement inside the wrapper
var reply = new Reply("ok", JsonDocument.Parse(json).RootElement);
Console.WriteLine(JsonSerializer.Serialize(reply));

record Order(string Customer, decimal Total);
record Reply(string Status, JsonElement Data);

Output:

"{\u0022Customer\u0022:\u0022Ballard Bakery\u0022,\u0022Total\u0022:42.50}"
{"Status":"ok","Data":{"Customer":"Ballard Bakery","Total":42.50}}
VS Code running DoubleSerialized.cs: serializing an existing JSON string produces \u0022 escaped quotes, while a JsonElement property embeds clean JSON
Line 1 is the double-serialized string; line 2 embeds the JSON as a JsonElement

With the relaxed encoder or Json.NET, the same bug looks like "{\"Customer\":...}". No encoder fixes it; serialize the object once, or return the existing JSON as-is.

When the JSON has to sit inside a bigger response, parse it into a JsonElement (or JsonNode) property, like the Data field above. Building the payload as a Dictionary in C# of plain values works too.

Is the Backslash Really in Your JSON String?

Sometimes there’s no bug. The Visual Studio debugger shows strings as C# literals, so the tooltip reads {\"Customer\":...} even though the string has no backslashes. Here’s the proof:

#:property PublishAot=false
using System.Text.Json;

string json = JsonSerializer.Serialize(new Order("Ballard Bakery", 42.50m));

Console.WriteLine(json);
Console.WriteLine($"Length: {json.Length}");
Console.WriteLine($"Has a backslash: {json.Contains('\\')}");

record Order(string Customer, decimal Total);

Output:

{"Customer":"Ballard Bakery","Total":42.50}
Length: 43
Has a backslash: False

The string is 43 characters with no backslash, the same check as when you check if a string contains a character in C#. Use the Text Visualizer (the magnifier icon) or print the value to see the real text.

Newtonsoft.Json: StringEscapeHandling Without Escape Characters

Json.NET (Newtonsoft.Json) has the opposite default. In a file-based app, #:package Newtonsoft.Json@13.0.4 pulls in the NuGet package:

#:package Newtonsoft.Json@13.0.4
#:property PublishAot=false
using Newtonsoft.Json;

Console.OutputEncoding = System.Text.Encoding.UTF8;
var order = new Order("Tom & Jerry's <Café>", 42.50m);

Console.WriteLine(JsonConvert.SerializeObject(order));
Console.WriteLine(JsonConvert.SerializeObject(order, new JsonSerializerSettings
{
    StringEscapeHandling = StringEscapeHandling.EscapeHtml
}));

record Order(string Customer, decimal Total);

Output:

{"Customer":"Tom & Jerry's <Café>","Total":42.50}
{"Customer":"Tom \u0026 Jerry\u0027s \u003cCafé\u003e","Total":42.50}
VS Code running NewtonsoftEscaping.cs: Newtonsoft.Json JsonConvert.SerializeObject keeps Tom & Jerry's <Café> unescaped by default, StringEscapeHandling.EscapeHtml adds \u0026 escapes
Json.NET doesn’t escape HTML characters unless you ask for EscapeHtml
StringEscapeHandlingWhat it escapesOur order
DefaultControl characters only (e.g. newline)Tom & Jerry’s <Café>
EscapeNonAsciiNon-ASCII + control charactersCafé becomes Caf\u00e9
EscapeHtml< > & ‘ ” + control charactersTom \u0026 Jerry\u0027s \u003cCafé\u003e

So with Json.NET you don’t change anything to avoid escapes. Older posts, this one’s first version included, said EscapeHtml removes them; it adds them. If you’re moving to System.Text.Json during an upgrade from .NET 8 to .NET 10, this default flip is the first surprise.

ASP.NET Core Minimal API: Set the JSON Encoder Once

In a web API you don’t call Serialize yourself, so set the encoder in ConfigureHttpJsonOptions. This app starts, calls its own endpoint and prints the response:

#:sdk Microsoft.NET.Sdk.Web
#:property PublishAot=false
using System.Text.Encodings.Web;

Console.OutputEncoding = System.Text.Encoding.UTF8;
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.Services.ConfigureHttpJsonOptions(o =>
    o.SerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping);

var app = builder.Build();
app.Urls.Add("http://127.0.0.1:0");          // any free port
app.MapGet("/order", () => new Order("Tom & Jerry's <Café>", 42.50m));
await app.StartAsync();

using var http = new HttpClient();
var response = await http.GetAsync($"{app.Urls.First()}/order");
Console.WriteLine(response.Content.Headers.ContentType);
Console.WriteLine(await response.Content.ReadAsStringAsync());
await app.StopAsync();

record Order(string Customer, decimal Total);

Output:

application/json; charset=utf-8
{"customer":"Tom & Jerry's <Café>","total":42.50}

The response is application/json; charset=utf-8, exactly the case where Microsoft says the relaxed encoder is fine. Property names come out camelCase because ASP.NET Core uses the web defaults. There’s more in my .NET 10 new features roundup.

Common JSON Escaping Errors and How to Fix Them

SymptomCauseFix
\u0026, \u0027, \u003C in outputDefault HTML-safe encoderJavaScriptEncoder.UnsafeRelaxedJsonEscaping (UTF-8 JSON only)
\u00E9 for é, \u5BFF for JapaneseNon-ASCII escaped by defaultJavaScriptEncoder.Create(UnicodeRanges.All)
“{\”Customer\”:…}” stringJSON string serialized againSerialize the object once, or use JsonElement
\” only in the debuggerDebugger shows a C# literalText Visualizer or Console.WriteLine
InvalidOperationException: Reflection-based serialization has been disabledFile-based app with native AOT on#:property PublishAot=false, or a JsonSerializerContext
é shows as ? in the consoleConsole code page, not the JSONConsole.OutputEncoding = Encoding.UTF8

Which JSON Escaping Option Should You Use?

SituationUse
API response or file read by codeDefault, or UnsafeRelaxedJsonEscaping if humans read it
Readable JSON with accents, never in HTMLUnsafeRelaxedJsonEscaping + WriteIndented
JSON that might land in a web pageJavaScriptEncoder.Create(UnicodeRanges.All)
Already have a JSON stringReturn it as-is, or embed it as JsonElement

Before you ship that bakery order API, these four tutorials fill in the data side:

Frequently Asked Questions

How do I serialize JSON without escape characters in C#?

Pass new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping } to JsonSerializer.Serialize. Quotes and backslashes inside strings are still escaped, because valid JSON requires it.

Is UnsafeRelaxedJsonEscaping safe to use?

It’s fine for JSON sent with a UTF-8 JSON content type. It doesn’t escape <, >, & or ‘, so never put its output straight into HTML or a script tag. See Microsoft’s System.Text.Json character encoding page.

Why does my JSON show \u0022 instead of quotes?

You serialized a string that was already JSON. Serialize the original object once, or store the JSON in a JsonElement property.

Does Newtonsoft.Json escape special characters?

By default only control characters. StringEscapeHandling.EscapeHtml adds HTML escapes and EscapeNonAscii escapes non-ASCII text.

How do I stop escaping in an ASP.NET Core API?

Call builder.Services.ConfigureHttpJsonOptions and set SerializerOptions.Encoder once. Every minimal API JSON response then uses it.