JSON layout is a UTF-8 text format for a hierarchical 2D layout: boxes, polygons, paths, texts, and cell instances. Coordinates are micrometres. Layer and datatype follow the GDS convention.
It is not a replacement for GDSII or OASIS, but intended for automated tests as AI can easily create it. Properties, nets, text height, fonts, circles as a special type, and the database unit itself are not stored. Use it for readable test layouts, small examples, and round-trips of the geometry the format covers.
A .json file can also be a DRC result (violations). That is a different schema, documented at the end of this page. File Open in the layout window accepts only a layout recipe.
| Action | Behaviour |
|---|---|
| File → Open | Replaces the drawing. The top cell of the file becomes the current cell. |
| File → Save / Save As | Writes the current cell as the root object. Every other cell in the drawing is listed in subcells (flat, not nested). Choose filter JSON layout (*.json *.JSON). |
| File → Attach | Adds the recipe cells to the existing drawing. Existing cells are not deleted. If a name already exists, an instance may bind to the older cell. |
| Update cells | For JSON this is the same load as Attach (cells are added, not replaced by name). |
| Text Editor → Execute | If the object has violations, the records are applied to the DRC list of the connected layout. Otherwise a layout recipe is applied: cells are added (the drawing is not cleared). A layout window must be connected. |
Extensions: .json, .JSON, .jsonc, .JSONC. The parser is strict JSON (RFC 8259). Comments and trailing commas are rejected, including in .jsonc files.
Save writes indented UTF-8. Empty arrays are omitted. Default instance fields are omitted. Extra keys that are not in this reference are ignored on load and dropped on save, so a private "comment" field can document a hand-written file.
Units. All layout coordinates, widths, and array steps are micrometres. They are converted with the current drawing database unit (default 1 nm, so 1.0 → 1000 dbu). The file does not store databaseunits or the user unit. Change the database unit before Open/Save only if you intend a different rounding.
Layers. Each shape has GDS layer and datatype integers. On load they are mapped into the LayoutEditor layer table (gdsAutoMapDatatypes). On save the mapped GDS layer/datatype are written back, not the internal layer index.
Scripting. drawingField::openFile / saveFile / importFile detect the type from the extension, for example layout->drawing->saveFile("/tmp/cell.json");.
License. JSON can be written in the reduced and node-locked modes, similar to CSV.
The file is a single JSON object (not an array). That object is one cell. Further cells are nested under subcells. Instance hierarchy is defined only by instances, not by how deeply subcells are nested. Nested subcells still become ordinary cells in the drawing; Save always flattens them into one subcells array on the root cell.
A file with no boxes, polygons, paths, texts, instances, or subcells is rejected. If it also contains violations, the error states that it is a DRC result, not a layout recipe.
Every cell object (root or subcell) uses the same keys.
| Key | Type | Default | Meaning |
|---|---|---|---|
cell |
string | "CASE" |
Cell name. |
boxes |
array of objects | omitted | Axis-aligned rectangles. |
polygons |
array of objects | omitted | Polygons. There is no polys key on load. |
paths |
array of objects | omitted | Centerline paths with width and cap. |
texts |
array of objects | omitted | Text anchors. |
instances |
array of objects | omitted | Cell references and arrays (GDS SREF / AREF). |
subcells |
array of cell objects | omitted | Further cells, same schema, nested arbitrarily. |
Unknown keys are ignored. Numeric fields accept integer or floating JSON numbers. On save, values that are whole numbers after rounding are written as integers.
boxes[]Axis-aligned box. x, y are the minimum X and minimum Y (origin). w and h are width and height, all in µm.
| Key | Type | Default | Required |
|---|---|---|---|
x |
number (µm) | 0 |
no |
y |
number (µm) | 0 |
no |
w |
number (µm) | 0 |
no |
h |
number (µm) | 0 |
no |
layer |
integer | 1 |
no |
datatype |
integer | 0 |
no |
{
"cell": "NW_WITH_WIDTH_RECT",
"boxes": [
{ "x": 0, "y": 0, "w": 3, "h": 0.4, "layer": 1, "datatype": 0 },
{ "x": 5.0, "y": 0.0, "w": 2.0, "h": 1.0, "layer": 1, "datatype": 0 }
]
}
polygons[]A polygon needs at least three vertices after conversion to database units; shorter outlines are skipped. The outline is not closed automatically. Save drops a last vertex that is identical to the first (GDS-style open list). Holes are not a separate construct; use another polygon or a boolean in the editor.
| Key | Type | Default | Required |
|---|---|---|---|
pts_um |
array of [x, y] (µm) |
empty | yes (or pts) |
pts |
same as pts_um |
— | alias if pts_um is missing |
layer |
integer | 1 |
no |
datatype |
integer | 0 |
no |
Each point is an array of at least two numbers: [x, y] in µm. Further entries in a point array are ignored.
{
"cell": "NW_WITH_WIDTH_POLY",
"polygons": [
{
"layer": 1,
"datatype": 0,
"pts_um": [
[0, 0],
[3, 0],
[3, 0.4],
[0.4, 0.4],
[0.4, 3],
[0, 3]
]
}
]
}
paths[]A path needs at least two vertices; shorter paths are skipped. Width 0 is stored and drawn as a zero-width path (the DRC engine ignores width 0 when it polygonizes paths).
| Key | Type | Default | Required |
|---|---|---|---|
pts_um |
array of [x, y] (µm) |
empty | yes (or pts) |
pts |
same as pts_um |
— | alias if pts_um is missing |
width |
number (µm) | 0 |
no |
width_um |
number (µm) | — | alias if width is missing |
cap |
integer | 0 |
no |
layer |
integer | 1 |
no |
datatype |
integer | 0 |
no |
cap is the GDS path type:
cap |
Ends |
|---|---|
0 |
Flush (square, ends at the vertices) |
1 |
Round |
2 |
Extend (square, ends extend by half the width) |
Save always writes pts_um, width, and cap (not the aliases).
{
"cell": "NW_TARGET_PATH",
"paths": [
{
"pts_um": [ [0.1, 0], [0.1, 2.0] ],
"width": 0.2,
"cap": 0,
"layer": 1,
"datatype": 0
}
]
}
texts[]A text is an anchor point plus a string. Height, font, presentation, and magnification are not stored. Save writes string from the element name.
| Key | Type | Default | Required |
|---|---|---|---|
x |
number (µm) | 0 |
no |
y |
number (µm) | 0 |
no |
layer |
integer | 1 |
no |
datatype |
integer | 0 |
no |
string |
string | "" |
no |
{
"cell": "LAB_OK",
"boxes": [
{ "x": 0, "y": 0, "w": 1.0, "h": 1.0, "layer": 1 }
],
"texts": [
{ "x": 0.5, "y": 0.5, "layer": 68, "datatype": 20, "string": "VDD" }
]
}
instances[]Places another cell. The named cell must exist in this file (subcells or, after Attach, already in the drawing). An empty cell string is skipped. A missing name is an error: instance cell not found: ….
| Key | Type | Default | Required |
|---|---|---|---|
cell |
string | — | yes (skipped if empty) |
x |
number (µm) | 0 |
no |
y |
number (µm) | 0 |
no |
mag |
number | 1 |
no; omitted on save when 1. 0 is ignored (treated as 1) |
angle |
number (degrees) | 0 |
no; omitted on save when 0. Counter-clockwise, GDS STRANS |
mirror |
boolean | false |
no; omitted on save when false. Mirror about X, applied before rotation |
nx |
integer | 1 |
no; values ≤ 0 become 1 |
ny |
integer | 1 |
no; values ≤ 0 become 1 |
step_x |
number (µm) | 0 |
column pitch along X; written when nx>1 or ny>1 |
step_y |
number (µm) | 0 |
row pitch along Y; written when nx>1 or ny>1 |
If nx>1 or ny>1, the instance is an array (GDS AREF). Pitch is orthogonal: step_x along X, step_y along Y. Diagonal array vectors are not stored.
Transform order matches GDS: mirror about X, then rotation, then magnification.
Single instance:
{
"cell": "TOP",
"instances": [
{ "cell": "LEAF", "x": 0, "y": 0, "angle": 90, "mirror": true, "mag": 2 }
],
"subcells": [
{
"cell": "LEAF",
"boxes": [ { "x": 0, "y": 0, "w": 0.2, "h": 2.0, "layer": 1 } ]
}
]
}
Array instance:
{
"cell": "TOP",
"instances": [
{ "cell": "LEAF", "x": 0, "y": 0, "nx": 2, "ny": 1, "step_x": 5, "step_y": 0 }
],
"subcells": [
{
"cell": "LEAF",
"boxes": [ { "x": 0, "y": 0, "w": 0.2, "h": 2.0, "layer": 1 } ]
}
]
}
subcells[]Each entry is a full cell object (same keys as the root). Load creates every nested cell first, then geometry, then instances, so instance order and subcell order do not matter as long as names exist. Duplicate cell names are not checked; findCell returns the first match.
Save does not reconstruct nesting: the current cell is the root, and all other cells of the drawing (referenced or not) appear in one subcells array.
Hierarchy example (two levels of instances):
{
"cell": "TOP",
"instances": [
{ "cell": "MID", "x": 0, "y": 0 },
{ "cell": "MID", "x": 5, "y": 0 }
],
"subcells": [
{
"cell": "LEAF",
"boxes": [ { "x": 0, "y": 0, "w": 0.2, "h": 2.0, "layer": 1 } ]
},
{
"cell": "MID",
"instances": [ { "cell": "LEAF", "x": 0, "y": 0 } ]
}
]
}
On load, each micrometre value is round(value × dbu_per_µm) to the nearest database unit.
On save, database units are converted back to µm. Values are rounded to 1×10⁻⁹ µm; near-zero becomes 0; values within 5×10⁻¹⁰ of an integer are written as integers.
A round-trip is not byte-identical: subcells are flattened, default fields are dropped, pts/width_um become pts_um/width, numbers may change between integer and float, and element order follows the drawing. Geometry of the supported types round-trips within the database-unit grid.
The following exist in the drawing (or in GDS) but are not part of this format. On save they are omitted. On load they cannot be expressed.
polygons)| Situation | Result |
|---|---|
| File unreadable | cannot read … |
| Invalid JSON | parser message |
| Root is not an object | JSON object expected |
| No geometry, instances, or subcells | no boxes, polygons, paths, texts, instances, or subcells |
Empty recipe that has violations |
DRC result JSON (violations), not a layout recipe |
| Instance name missing in the drawing | instance cell not found: … |
| Save with no current cell | no cell |
| Save cannot create the file | cannot write … |
Polygons with fewer than three points and paths with fewer than two points are skipped without an error.
Execute in the Text Editor on a JSON tab treats an object that contains violations as DRC output, not as a layout. File Open never loads this schema as geometry.
Coordinates here are integer database units, not micrometres.
Root (object or a bare array of records):
| Key | Type | Meaning |
|---|---|---|
cell |
string | Optional. Cell the markers belong to; used to select that cell if it exists. |
violations |
array | Marker records. If the root is an array, that array is the violation list. |
Each record:
| Key | Type | Meaning |
|---|---|---|
rule |
string | Check id (for example NW.W.1). |
distance_dbu |
integer | Measured distance in dbu. |
bbox |
[x1, y1, x2, y2] |
Integer bounding box in dbu. |
kind |
string | "edge_pair" or "bbox". |
e1, e2 |
[[ax, ay], [bx, by]] |
Edge pair in dbu when kind is edge_pair. |
{
"cell": "NW_WITH_WIDTH_RECT",
"violations": [
{
"rule": "w.fail",
"distance_dbu": 200,
"kind": "bbox",
"bbox": [0, 0, 200, 2000]
}
]
}
The same schema is used as expect.json in DRC test cases (layout --drcverify / --drcselftest). Running a .ledrc file from the Text Editor writes this JSON; Execute on that result fills the violation list. See Checking Designs and the .ledrc reference.
.json files that are not layouts| File | Role |
|---|---|
layout.json |
This layout recipe. Open/Save in the layout window. |
expect.json |
DRC result / oracle (violations). Not a layout. |
meta.json |
DRC test-case metadata (id, ops, oracle, …). Not a layout. |
twin_layers.json |
Optional KLayout twin mapping for --drcverify. Not a layout. |
Opening expect.json or meta.json from the layout window reports an error. Open them in the Text Editor instead.
{
"cell": "TOP",
"boxes": [
{ "x": 0, "y": 0, "w": 10, "h": 4, "layer": 1, "datatype": 0 }
],
"polygons": [
{
"layer": 2,
"datatype": 0,
"pts_um": [ [1, 1], [3, 1], [2, 2.5] ]
}
],
"paths": [
{
"pts_um": [ [0, 5], [4, 5], [4, 8] ],
"width": 0.3,
"cap": 2,
"layer": 1,
"datatype": 0
}
],
"texts": [
{ "x": 1, "y": 6, "layer": 10, "datatype": 0, "string": "out" }
],
"instances": [
{ "cell": "VIA", "x": 8, "y": 1, "nx": 3, "ny": 2, "step_x": 0.5, "step_y": 0.5 }
],
"subcells": [
{
"cell": "VIA",
"boxes": [
{ "x": 0, "y": 0, "w": 0.2, "h": 0.2, "layer": 3, "datatype": 0 }
]
}
]
}