# JsonSerializer

> Turn a value into a JSON string and a JSON string into a typed object — the C#-faithful System.Text.Json spelling. `JsonSerializer.Serialize(order)` gives the JSON text; `JsonSerializer.Deserialize<Order>(body)` parses it back into a typed `Order`. Pure — no capability needed.

<!-- id: json-serializer · area: json · stability: stable · html: https://osysharp.com/reference/json/serializer/ -->

## Summary        {#summary}
**`JsonSerializer`** is the JSON surface, spelled exactly as in C# (`System.Text.Json`). It has two members:

```osy syntax
var body = JsonSerializer.Serialize(order);              // a value → a JSON string
var order = JsonSerializer.Deserialize<Order>(body);     // a JSON string → a typed Order
```

It is **pure** — no capability, no `using` required (though a pasted `using System.Text.Json;` is accepted and does
nothing). It pairs naturally with [Http.*](https://osysharp.com/reference/http/facade/): serialize a request body, deserialize a response.

## Signature      {#signature}
```osy syntax
string JsonSerializer.Serialize(value)          // value = a scalar, a class, a list, or a map
T      JsonSerializer.Deserialize<T>(string json) // T = a class
```

## Description    {#description}
**`Serialize`** accepts any value and returns compact JSON (matching System.Text.Json's default): a scalar
(`"x"`, `42`, `true`, `3.5`), a **class** instance (→ a JSON object of its fields), a **list** (→ an array), a **map**
(→ an object). Property names are the field names as declared. `byte[]` becomes base64.

**`Deserialize<T>`** parses JSON into a `T`, where **`T` is a `class`** — the DTO you want the data as. It fills each
declared field from the matching JSON member, coercing by the field's type: scalars, a nested class (→ a nested
object), and a collection (→ a list of elements). A member missing from the JSON is left at its default; a JSON `null`
sets null. A field the class doesn't declare is ignored.

`T` must be a **class**, not an `entity` — an entity has identity and persistence a JSON body can't carry.

### Storing it: the `Json` property type     {#json-column}
A property declared `Json` holds a document, and it is **string-backed** — so `Serialize` writes one and the property
reads back into `Deserialize`:

```osy syntax
class Detail { public int Attempt; public string Reason; }

entity Job { [Required, MaxLength(50)] string Name; Json Detail; }

void Record() {
  var j = new Job { Name = "j1", Detail = JsonSerializer.Serialize(new Detail { Attempt = 2, Reason = "retry" }) };
}

int Attempts(Job job) {
  return JsonSerializer.Deserialize<Detail>(job.Detail).Attempt;
}
```

That is the whole surface: a `Json` property takes a string and gives one back. It is **not parsed or validated** on
the way in — the same rule a `Markdown` property follows — so `Serialize` is how you sensibly produce one rather than
building the text by hand.

⚠ There is no object-literal form: `Detail = new { attempt = 2 }` does not compile, because an anonymous object is
not a value in Osy#. Declare the shape as a `class` and serialize it, which is what you would do in C# anyway and
gives the document a name the rest of your code can use.

**Serializing an `entity`** produces a **shallow** object: its `Id` and scalar properties, with an `EntityRef`
rendered as its FK id (not the nested entity) and collections omitted. This is deliberate — expanding relations by
default would invite reference cycles and load a record's whole object graph. When you want a specific nested shape,
map the entity into a `class` DTO (which serializes fully) and serialize that.

### How deep may a value nest, and what about a cycle?     {#depth-and-cycles}
Nesting is capped at **500 levels** — a class whose field is a class whose field is a class, a list of lists, a map
of maps. Past that, `Serialize` refuses with an error naming the depth, the limit and the class it stopped inside.
The cap is on nesting only: a list of a million flat items is depth 2 and serializes fine.

A value that **refers back to itself** is refused too, with its own message rather than a depth one — JSON has no way
to write a reference to a value it has already written, so there is nothing faithful to produce:

```text
JsonSerializer.Serialize cannot serialize an instance of class 'Node', because it refers back to itself — JSON has
no way to write a reference to a value it has already written. Break the cycle before serializing (drop the
back-pointer, or serialize the id instead of the object).
```

The same value reached twice down **different** branches is not a cycle: it is written out once per occurrence, as
you would expect. See [How deep can an object graph get?](https://osysharp.com/reference/function/deep-object-graphs/) for how the limit is counted and why it exists.

## Examples       {#examples}

Round-trip a DTO through JSON:

```osy title="round-trip a DTO" test app=json-serializer
class Order {
  public string Code;
  public decimal Total;
  public bool Paid;
}

string ToJson(Order o) {
  return JsonSerializer.Serialize(o);              // {"Code":"A1","Total":42.0,"Paid":true}
}

Order FromJson(string body) {
  return JsonSerializer.Deserialize<Order>(body);  // typed Order, fields populated
}
```

Parse an HTTP response body ([Http.*](https://osysharp.com/reference/http/facade/)):

```osy title="parse an HTTP response body" test app=json-serializer
app Shop {
  model "model/**/*.osy";
  use Osysharp.Http;       // `use` is a manifest declaration — Http.* needs it
}

class Weather { public decimal TempC; public string Summary; }

Weather Fetch(string city) {
  var r = Http.Get("https://api.example.com/weather/" + city);
  return r.IsSuccess ? JsonSerializer.Deserialize<Weather>(r.Body) : new Weather { Summary = "unknown" };
}
```

Nested classes and lists deserialize recursively:

```osy title="nested classes and lists" test app=json-serializer
class Line { public string Sku; public int Qty; }
class Cart { public string Owner; public List<Line> Lines; }

Cart Parse(string body) {
  return JsonSerializer.Deserialize<Cart>(body);   // Lines becomes a list of typed Line objects
}
```

## See also       {#see-also}
- [Json](https://osysharp.com/reference/types/json/) — the `Json` property type this writes into, and when to reach for it
- [Http.*](https://osysharp.com/reference/http/facade/) — the outbound HTTP surface whose bodies this serializes / parses
- [constructor](https://osysharp.com/reference/class/constructors/) — the `class` types Serialize walks and Deserialize targets
- [How deep can an object graph get?](https://osysharp.com/reference/function/deep-object-graphs/) — how deep a value may nest before Serialize refuses it, and what happens to a cycle
