Skip to main content

The canonical JSON schema

A key-by-key reference for canonical JSON. Every example here was produced by actually running the tool on a bundled building.

What is omitted​

A key with no value is not emitted. muro, h, slab, t, edge, attrs, line and the rest appear only if the source wrote them. Empty blocks (assets, polygons, columns, zones, areas, openings, segs) are not emitted either.

Attribute values are numbers if they have numeric form, strings otherwise. underground is emitted as 1 only when true; air is emitted as true only when true.

Top level​

The key order is fixed.

KeyPresentContent
formatalwaysthe version of this format itself; currently "koyu-canonical/2.0"
muroonly with a version declarationthe language version, passed through. Spelled muro whatever word the source used — a file written koyu 1.1 still says "muro": "1.1", because the key names the thing being versioned
modeonly with mode spatial"spatial"; absence means the building reading. Consumers must not infer walls for a spatial model
nameif name was writtenthe name of the building
unitalwayscurrently "mm" only
originif origin was written{ epsg, easting, northing, elevation?, vertical? } — metres, not millimetres
azimuthif azimuth was writtena number: the true bearing of +Y, clockwise from true north, 0 ≤ v < 360
gridalways{ "X": [coords…], "Y": [coords…] } — the names X1.. are implicit
levelsalwaysname → { z, h?, slab?, underground? }; keys in collation order
assetsif declaredname → { kind, attrs? }; keys in collation order
polygonsif declaredpath → [[x, y], …]; keys in collation order
columnsif declaredan array, in declaration order
zonesif declaredpath → { attrs? }; keys in collation order
spacesalwayspath → space; keys in path collation order
boundariesalwaysan array; lexicographic by between, ties by canonical content

origin and azimuth​

"origin": { "epsg": 6677, "easting": -8000.123, "northing": -34000.456, "elevation": 2.35, "vertical": 6695 },
"azimuth": 347.5

The two halves of the model's frame. They come before grid because they say what the coordinate system grid sets up actually means.

epsg and vertical are EPSG codes, carried through without being resolved. easting, northing and elevation are in metres — the one place in the format where a length is not millimetres, because these are coordinates in a foreign frame rather than lengths inside the model.

origin is emitted whole or not at all, but elevation and vertical are a pair inside it: both or neither. An absent azimuth means the model has no bearing — it does not mean zero.

Nothing is derived from either. Neither key changes an area, an adjacency or a shape, and neither appears in the derived Form.

grid​

"grid": { "X": [0, 3600, 7200], "Y": [0, 4500] }

Only the coordinate arrays. Grid names are implicitly X1, X2, … from the front, so they are not spelled.

levels​

"levels": {
"B1": { "z": -5100, "h": 4200, "slab": 900, "underground": 1 },
"L1": { "z": 0, "h": 3600, "slab": 600 }
}
KeyMeaning
zFL height in mm. Always present
hceiling height in mm
slabfloor construction depth in mm
undergroundthe declaration that a level is below ground. Never inferred from a negative z — emitted as 1 only when written

assets​

"assets": {
"WM": { "kind": "component", "attrs": { "category": "appliance", "d": 600, "plan-svg": "./wm.svg", "w": 600 } }
}

kind is door, window, or component. Openings reach door and window assets through ref. A named area reaches a component through its asset attribute. plan-svg remains a relative path in canonical JSON; the SVG bytes are drawing input and never enter this format.

polygons​

"polygons": { "/site": [[-2600, -7000], [38000, -7000], [38000, 19600], [2000, 21000], [-2600, 15000]] }

A given, and the one geometry written into the source. The vertex list is geometry (cyclic) and is never reordered.

columns​

"columns": [
{ "size": 900, "levels": ["B2", "B1", "L1", "L2", "L3", "L4", "L5", "L6"] },
{ "size": 800, "levels": ["L7", "L8", "L9", "L10", "L11", "L12", "L13"] }
]
KeyMeaning
sizesection width in mm
ddepth in mm. Omitted when it equals size
levelsthe levels the column stands on
x / ythe grids it targets. Omit for all grids. Ordered by grid order (x:X2,X1 and x:X1,X2 are the same composition)
attrsattributes

This is the one array that is never sorted. Two columns never stand on the same intersection and the earlier declaration wins, so declaration order is the composition itself.

zones​

"zones": { "/L3/A": { "attrs": { "lease.category": "exclusive", "name": "Aタイプ" } } }

A zone carries attributes only. No region, no type.

spaces​

"/L1/s1": {
"type": "shop",
"at": [
["X1", "Y1-4600", "X3", "Y2"],
["X3", "Y1-4600", "X4", "Y1"]
],
"attrs": { "floor": "モルタル", "lease.category": "rentable", "name": "店舗1" },
"areas": [
{ "at": ["X1", "Y1-4600", "X2", "Y1-2600"], "attrs": { "floor": "タイル", "name": "土間" } }
]
}
KeyPresentContent
typewhen writtenthe type of the space. Optional, so the key is absent when none was written (no default is fabricated)
levelonly when explicitemitted only when the level differs from the first path segment (a maisonette, say). The default is omitted
atif there is a regionfour grid names. A flat quadruple for a single rectangle, an array of quadruples for several, sorted by canonical content
attrsif there are attributeskeys in collation order
areasif area was writtenan array of { at, attrs? } in canonical content order

The quadruple is [X start, Y start, X end, Y end]. A reversed notation (X2..X1) is normalised to ascending coordinates.

A component placement is represented entirely by ordinary area attributes: name, asset, align-x, align-y, offset-x, offset-y, and rotate. Canonical JSON records those written constraints, not the derived centre, rotated footprint, or loaded SVG artwork.

boundaries​

{
"between": ["/home/hall1", "/home/ldk"],
"a": "/home/ldk",
"kind": "wall",
"t": 120,
"attrs": { "spec": "LGS" },
"openings": [
{
"kind": "door", "ref": "SD1", "w": 800, "h": 2000,
"at": 0.5, "edge": "E", "hinge": "S", "swing": "b",
"attrs": { "name": "片引き戸", "style": "sliding" }
}
]
}
KeyPresentContent
betweenalwaysthe two paths in ascending order. This is the identity of the relation
aalwaysthe written direction — the space written first. edge and swing are read from that side. Without it, the swing cannot be recovered from JSON alone
kindalwayswall / open / stair / shaft / void
tif writtenwall thickness in mm
aironly when truetrue. Something that does not enclose (a rail, a fence)
edgeif writtenN / E / S / W — narrows the segments to one face seen from a
lineif writtenthe endpoint pair of a drawn line, as grid words, in ascending resolved coordinates
attrsif there are attributesspec, fire, h, …
openingsif there are openingscanonical content order
segsif there are segscanonical content order

The top height of an air:1 boundary is emitted as an attribute.

{
"between": ["/home/hall2", "/home/void"],
"a": "/home/void",
"kind": "wall", "t": 120, "air": true,
"attrs": { "h": 1100, "spec": "手すり" }
}

A boundary with a drawn line.

{
"between": ["/L1/load", "/out"],
"a": "/L1/load",
"kind": "wall", "t": 300,
"line": ["X19-4200,Y12", "X19,Y12-2100"],
"attrs": { "spec": "RC" }
}

A segment has no direction, so the endpoint pair is canonicalised to ascending resolved (x, then y). The spelling stays exactly the grid reference that was written; only the order swaps.

Default boundaries do not appear here. Touching spaces default to a wall, but canonical JSON holds only the written composition.

openings​

KeyPresentContent
kindalwaysdoor / window
refwhen an asset is usedthe asset name
walwayswidth in mm
hif writtenheight in mm
atalwaysa number for a ratio, a string for a grid reference. Unwritten it is 0.5
edgeif writtenN / E / S / W
hingeif writtenthe hinge side
swingif writtenthe side it opens into, a / b
attrsif there are attributesname, style, … Attributes inherited from an asset are expanded and emitted too

segs​

"segs": [
{ "w": 3600, "at": 0.75, "attrs": { "spec": "ガラスパーティション" } }
]
KeyPresentContent
walwayswidth in mm
atalwaysa number for a ratio, a string for a grid reference
edgeif writtenN / E / S / W
attrsif there are attributesan interval override such as spec

A seg is an uncounted segment — it appears in no area and no graph, and carries only an override over an interval.

Neighbouring pages​