jxn 0.3 명세
jxn(JSON eXplained Notation)은 JSON 의 형태와 뜻을 정의하는 언어(스키마 언어)다 -- 사람이 읽는 정의에 뜻과 관계를 적고, JSON Schema 로 옮긴다. 이 문서는 jxn 0.3 의 명세다. 2026-09-27 확정. 1.0 전(0.x)에는 뒤로 깨는 변경이 있을 수 있다 -- 바뀐 것은
CHANGELOG.md에 적는다. 뼈대는 JSON 처럼 쓴다 (0.2) -- 정의를 보면 인스턴스의 모양이 보인다. 뜻 · 주석 · 관계는 키의 옆과 아래에 붙는다. 결정은notes/문법-0.2-결정.md. 0.3 은 0.2 에 더하기만 했다 -- 글 속의 링크와 키 참조 (2-16, 결정notes/링크-결정.md).jxn 0.2파일은 그대로 읽힌다.
- 예는 전부 합성이다 (주문 · 상품 · 카탈로그 · 주문 이력).
```jxn블록은 완결된 파일이다 -- 참조 구현의 시험이 읽어 진단이 없어야 한다.```text블록은 조각이다.- 0.1 · 0.2 파일은
jxn upgrade FILE.jxn으로 옮긴다 (6절). 옛 판 명세는spec/jxn-0.1.md·spec/jxn-0.2.md에 남아 있다.
한 줄 원칙: { } 는 object, [ ] 는 배열, | 줄은 갈래와 값 목록, 따옴표는 인스턴스에 나오는 키와 값, 맨 낱말은 타입, // 는 뜻, @ 는 주석, 관계 낱말로 시작하는 줄은 관계.
0. 한눈에#
파일로도 있다 -- examples/order/order.jxn (인스턴스 order.json).
jxn 0.3
// # 목록과 주석
list owner // 닫힌 목록 (정의 파일이 스스로 선언한다)
| customer // 고객이 넣은 값
| system // 시스템이 계산한 값
| warehouse // 창고 시스템이 돌려준 원문
tag owner : set<owner> inherit need // @owner 주석. 조상에서 이어받고, 모든 키에 있어야 한다
tag status need // jxn 이 주는 status 를 모든 키에 요구한다
anchor change = $.history : Change // 이웃 주소 change[-1] = 바로 앞 Change (Note 는 건너뛴다)
anchor catalog external // 인스턴스 밖의 것
// # 주문
{ // 주문 하나
@owner system @status stable
"id": string, // 주문 번호
@status stable
"items": [ Item ] min 1, // 담은 상품
@owner customer @status stable
"total": number, // 합계 금액
@status stable
derived-from items[*].price // 담은 상품 값의 합
"method": "card" | "cash", // 결제 방법
@status stable
"payment"?: by method // 결제 -- 방법이 모양을 정한다
@status draft
| "card" => { "last4": string } // 카드 끝 네 자리
@status draft
// 한 줄 object 의 키(last4)는 이 갈래의 주석을 쓴다
| "cash" => absent // 현금이면 없다
"history": [ Change | Note ] // 주문 이력
@status stable
// 고치지 않고 붙이기만 한다. 갈래는 어느 키가 있나로 갈린다
}
def Item { // 담은 상품 하나
@status stable
"sku": string, // 상품 코드
@status stable
refers-to catalog.items
"price": number // 담을 때의 값
@status stable
}
def Change { // 단계가 바뀐 기록
@status stable
"from": State, // 바뀌기 전 단계
@status stable
equals change[-1].to // 바로 앞 변경의 도착 단계에서 출발한다
"to": State // 바뀐 단계
@status stable
}
def Note { // 사람이 남긴 메모
@owner customer @status draft
"text": string // 메모 글
@status draft
}
def State = "open" | "paid" | "shipped" // 주문의 단계
@status stable같은 모양의 인스턴스:
{
"id": "A-1",
"items": [{ "sku": "P-10", "price": 1200 }, { "sku": "P-11", "price": 300 }],
"total": 1500,
"method": "card",
"payment": { "last4": "4242" },
"history": [{ "from": "open", "to": "paid" }, { "text": "포장 부탁" }]
}1. 파일과 줄 -- 문법은 줄 단위다#
- 파일은 UTF-8 글이고 확장자는
.jxn이다. 줄 끝은 LF · CRLF 둘 다 된다. 빈 줄은 뜻이 없다. - 한 줄은 첫 글자 · 첫 낱말로 종류가 정해진다. 들여쓰기는 모양일 뿐 뜻이 없다 -- 블록은
{}가, 갈래 · 값 목록은|줄이 가른다. - 키 줄 · 닫는 줄 ·
|줄 끝의 쉼표 하나는 JSON 처럼 써도 되고 빠져도 된다.
| 줄 | 꼴 | 뜻 |
|---|---|---|
| 머리 | jxn 0.3 |
표기의 판. 첫 코드 줄이다 (jxn 0.2 도 된다) |
| 뿌리 | { ... } |
인스턴스의 뿌리 object (2-3) |
| 키 | "key": T · "key"?: T · "key": by k · "key": |
이름이 붙는 자리 (2-4) |
| 선언 | def N ... · list N · tag N ... · anchor N ... · home 주소 |
맨 위 수준 |
| 갈래 · 값 | | "v" => T · | "v" · | 낱말 |
위 선언의 갈래 · 값 목록 · 닫힌 목록 (2-1 · 2-6 · 2-8) |
| 주석 | @tag 값[, 값] [@tag 값 ...] |
바로 앞 선언에 붙는다. 한 줄에 여럿 |
| 관계 | [.sub] 낱말 주소 [when 조건] |
바로 앞 선언이 주어다 (2-11) |
| 펼치기 | ...Name |
Name 의 키를 이 object 에 펼친다 |
| 닫기 | } · }] · } } |
블록을 닫는다. 뒤의 글자는 열 때의 타입식을 마저 닫는다 ([{ ... }]) |
| 말 | // 글 (줄 전체) |
풀이. 바로 앞 선언(또는 머리)에 붙는다 |
| 머리 | // # 글 (줄 전체) |
무리를 가른다 -- 같은 블록 · 목록에서 다음 머리까지의 형제를 묶는다. 맨 위 수준이면 절 |
| 물음 | // ? 글 (줄 전체) |
열린 물음. 바로 앞 선언에 붙고, 도구가 목록으로 모은다 |
| 꾸밈 | // ==== 처럼 / - = 로만 된 줄 |
버린다 |
- 줄 끝의
//는 그 줄의 뜻이다 -- 선언이면 description, 관계면 note, 값이면 그 값의 description. 따옴표 밖에서, 줄 머리이거나 앞이 공백인 첫//에서 자른다. 뜻 안의--는 그냥 글이다. - 뜻은 한 줄, 풀이는 여러 줄. 선언의 뜻(한 문장)은 줄 끝
//에만 쓴다. 더 할 말은 뒤따르는 말 줄로 쓴다. - 뜻 · 풀이 · 물음 같은 글 안의 링크
[글](주소)·https://...와 키 참조는 2-16. - 주석 · 관계 · 말 · 물음은 바로 앞 선언에 붙는다.
{를 연 선언이면 그 블록 안의 첫 선언 전까지 그 선언의 것이다 -- 블록 전체에 대한 말은 블록 첫머리(여는 줄 바로 아래)에 쓴다. |줄이 이어지는 동안 그 아래의 주석 · 관계 · 말은 그|줄의 것이다. 선언 자신의 주석은 첫|줄 앞에 쓴다.|목록에는 닫는 줄이 없다 --|가 아닌 다음 선언 줄(키 ·}·by· 맨 위 선언)에서 끝난다.- 머리 줄 바로 뒤의 말 줄(다음 선언 전까지)은 그 머리의 풀이다.
jxn머리 줄보다 위의 말 줄은 파일의 풀이다. }뒤에는 앞 선언이 없다 -- 닫은 뒤에 오는 주석 · 관계 줄은 오류다.
2. 구성 -- 꼴 · 뜻 · 예 · JSON Schema#
2-1. 닫힌 목록 list#
list owner // 누가 채우나
| customer // 고객이 넣은 값
| "external api" // 공백이 있으면 따옴표
| 0 // 수도 된다- 정의 파일이 제 주석 값의 어휘를 선언한다 (jxn 은 틀만 준다). 값마다 뜻을 단다. 인스턴스의 값이 아니라서 낱말 그대로 쓴다.
- 값의 JSON 타입 -- 따옴표 없는 수는 number, 나머지는 string (
"0"은 string). - JSON Schema: 목록 자체는 나가지 않는다. 그 목록을 값으로 쓰는 주석이
x-owner로 나가고,jxn check가 값이 목록 안인지 본다.
2-2. 주석 선언 tag#
tag note : text // 글
tag src : set<text> // 글 여럿
tag owner : set<owner> inherit need // 목록의 값 하나 이상
tag audience: set<role> | all inherit need // 목록 값들, 또는 낱말 all 홀로
tag stage : stage inherit // 목록의 값 하나
tag status need // jxn 이 준 주석에 방식만 더한다 (2-15)- 값의 꼴:
text· 목록 이름(하나) ·set<목록>·set<text>(쉼표로 여럿) · 낱말(홀로 쓰는 값).|로 잇는다. - 방식:
inherit= 가장 가까운 조상의 값을 이어받는다.need= 이어받기를 푼 뒤 모든 키에 있어야 한다. 둘 다 없으면 선택. - 쓰는 곳:
@owner customer, system· 한 줄에 여럿@owner system @status stable. 공백이 든 값은 따옴표 --@note "두 낱말". text·set<text>꼴의 값은 링크 하나일 수 있다 --@src [주문 규격 4절](docs/주문.md), https://example.com/spec. 링크의 글에 공백 · 쉼표가 있어도 따옴표 없이 값 하나다 (2-16).- 이름
status는 jxn 이 준다 -- 파일이list status나 타입을 단tag status : ...를 선언하면 오류다. - JSON Schema:
x-<tag>. 값이 하나면 스칼라, 여럿이면 배열.
2-3. 뿌리 { } 와 def#
{ // 인스턴스의 뿌리 -- 파일에 하나. 이름이 없다
"id": string
}
def Item { // 이름 붙은 object
"sku": string
}
def Address { "street": string, "city": string } // 한 줄 object
def Method = "card" | "cash" // 이름 붙은 타입식
def Money = number min 0 // 주석 · 관계 줄이 뒤따를 수 있다
@status stable
def State = // 값마다 뜻을 다는 값 목록 (2-6)
| "open" // 담는 중
| "paid" // 결제됨- 인스턴스가
{로 시작하듯 정의도 이름 없는{로 시작한다. 주소에서 뿌리는$다 (2-12). - def 이름은 대문자로 시작한다 -- 타입식에서 기본 타입 · 낱말과 갈린다.
- JSON Schema: 뿌리 -> 문서의 뿌리 (
title은 파일 이름),def->$defs/<이름>, 쓰는 곳은$ref.
2-4. 키 -- 늘 있다 · 없을 수 있다 · null 일 수 있다#
"id": string // 늘 있다 -> required
"note"?: string // 없을 수 있다 -> required 에 넣지 않는다
"coupon": null | Coupon // 늘 있되 null 일 수 있다 -> required + anyOf [null, ...]
"ship to": Address // 키 글자는 JSON 문자열 -- 공백 · 한글도 된다- 키는 늘 따옴표로 쓴다 -- JSON 과 같다. 키 글자는 JSON 문자열 규칙을 따른다 (
\"·\n·\u..). ?는 키가 없을 수 있다는 표시다 (TypeScript 의?:와 같다). null 은null |로 따로 적는다 -- "칸은 늘 있고, 할 일이 없으면 null" 인 기록에서 이 차이가 뜻이다.|의 차례에는 뜻이 없다 (null | T=T | null). 여러 줄 object 에서null | {처럼 앞에 두기를 권한다.
2-5. 타입식#
| 꼴 | 뜻 | JSON Schema |
|---|---|---|
string integer number boolean |
기본 | type |
any |
무엇이든 -- 일부러 열었다 | {} |
unknown |
타입을 아직 모른다 | {} + x-jxn-unknown: true |
"a" | "b" · 0 · true |
허용 값 -- 따옴표는 인스턴스에 나오는 실제 값이다 | enum (전부 값 · null 이면) · 아니면 anyOf |
A | B |
둘 중 하나 | anyOf (object def 둘 이상이면 2-9) |
[ T ] |
배열 | type: array, items |
{ [키의 뜻]: T } |
키가 열린 object (map). 어떤 키든, 그 키는 [ ] 안의 뜻 |
additionalProperties: T + x-jxn-key |
{ ... } · {} |
닫힌 object -- 적힌 키만 | additionalProperties: false (갈래 · 펼치기가 있으면 unevaluatedProperties: false) |
{ "a": T, "b"?: U } |
한 줄 object -- 쉼표로 가른다. 안쪽 키에는 뜻 · 주석 · 관계를 못 단다 -- 그 object 가 놓인 자리(키 · 갈래 · 쓰는 자리)의 주석을 이어받지 않는 것까지 그대로 쓴다 | 위와 같다 |
T min n · T max n |
범위 | 수 minimum · 배열 minItems · 글 minLength · map minProperties (max 도 같다) |
expect T |
기대하는 모양 (2-10) | x-jxn-expect: <T> -- 표준 검사기는 보지 않는다 |
absent |
이 갈래에서는 키가 없다 (갈래에서만) | properties: {key: false} |
"lines": [{ // 여러 줄 object 의 배열은 [{ 로 열고 }] 로 닫는다
"sku": string,
"qty": integer min 1
}] min 1,
"stock": { [sku]: integer }, // 상품 코드마다 재고
"values": { [value id]: { // 여러 줄 object 의 map 은 } } 로 닫는다
"value": any
} }- 맨 낱말은 타입이다 (
string·Item). 따옴표 글은 인스턴스의 실제 값이다 ("card"). - 한 곳에서만 쓰는 모양은 그 자리에 풀어 쓰고 (
[{...}]), 두 곳 이상이면 def 로 뺀다 -- 정의가 인스턴스처럼 중첩되어 읽힌다.
2-6. 값마다 뜻을 다는 값 목록 |#
"state": // 주문의 단계
@status stable
// 반품은 다음 판에서 정한다 (state 의 풀이 -- 첫 | 줄 앞)
// # 담는 동안
| "open" // 담는 중
| "held" @since 2 // 잠시 멈춤
// # 값을 치른 뒤
| "paid" @since 2 // 결제됨
// 부분 결제는 아직 없다 (paid 의 풀이)
| "shipped" @status draft // 보냄- 키(또는
def N =)의 타입 자리를 비우고, 다음 줄부터| 값을 쓴다. 한 줄에 쓰면"state": "open" | "held" | ...-- 같은 것의 두 꼴이다. 값마다 뜻 · 주석을 달고 싶을 때 여러 줄로 쓴다. - 값 줄 =
| 값· (선택) 한 줄 주석 · 줄 끝//뜻. 값은 JSON 값이다 -- 글은 따옴표, 수는 그대로. - 머리(
// #)로 값을 무리로 가를 수 있다 --jxn doc이 무리째 보인다. - JSON Schema:
enum: [...]+x-jxn-values: [{value, description, x-...}].
2-7. 펼치기 ...Name#
def Address { "street": string, "city": string }
"shipTo": {
...Address,
"note"?: string // Address 에 키 하나를 더한다
}- JSON Schema: 안에 풀어 넣는다. 닫힌 def 를
$ref로 부르면 키를 더할 수 없다 (2020-12 에서additionalProperties: false는$ref너머를 모른다).$defs가 겹쳐 나오는 것은 감수한다.
2-8. 갈래 by#
키 하나의 모양을 이웃 키가 정한다 (필드 갈래):
"method": "card" | "cash" | "voucher",
"payment"?: by method // 결제 내용 -- method 의 값에 따라
| "card" => { "last4": string } // 카드 끝 네 자리
| "voucher" => Voucher
| "cash" => absent // 현금이면 payment 키가 없다object 의 키 집합을 한 키가 정한다 (object 갈래):
def Discount {
"kind": "percent" | "fixed"
by kind
| "percent" => { "rate": number } // 이 갈래에서만 rate 가 있다
| "fixed" => { "amount": number }
}- 갈래 줄 =
| 값[, 값 ...] => 모양-- 같은 모양의 값은 한 줄에 묶는다. 이름표는 판별 키의 실제 값이라 따옴표로 쓴다.=>는 "이면". - 모양이 여러 줄이면
| "user" => {로 열고}로 닫는다. - 값 목록의 정본은 판별 키의 타입이다. 갈래의 이름표는 그 값을 빠짐없이 · 남김없이 · 겹침없이 덮어야 한다 -- 어긋나면 오류다. 필드 갈래에서 키가 없는 값은
absent로 적고, 그 키는?여야 한다. - 갈래에도 주석 · 관계가 붙는다 (그
|줄 아래). 갈래 안에 적은 관계는 그 갈래의 조건을when에 저절로 더한다 --jxn doc은 합친 조건을 보인다. - JSON Schema: 부모 object 의
allOf에 갈래마다{if: {properties: {method: {enum: [card]}}, required: [method]}, then: {properties: {payment: <T>}, required: [payment]}}. 부모는unevaluatedProperties: false.
2-9. 키가 있나로 갈리는 합 A | B#
"history": [ Change | Note ] // Change 는 from 이, Note 는 text 가 있다- 판별 값이 없다 -- 어느 키가 있나로 갈린다. object def 둘 이상의 합은 서로를 가르는 "반드시 있는 키" 가 있어야 한다 -- 없으면 오류다.
- JSON Schema:
anyOf. 도구는 갈래 이름을 def 이름에서 짓는다.
2-10. 기대하는 모양 expect T#
"reply": expect StockReply, // 창고가 돌려준 원문. 모양이 어긋나도 그대로 적힌다
"replyCheck": [ string ] // 어긋난 칸 -- 어긋남은 여기가 말한다- 밖에서 온 원문을 그대로 남기는 칸을 위한 것이다. 엄격히 검사하면 "어긋난 원문을 남겼다" 는 올바른 인스턴스가 떨어진다.
- JSON Schema:
x-jxn-expect: <T>-- 표준 검사기는 무엇이든 통과시킨다. jxn check: 그 자리를 T 로 따로 대어 보고, 어긋남을 오류가 아닌 "어긋남" 급으로 낸다 (종료 코드 0).--strict면 오류다.
2-11. 관계#
키와 키 사이의 뜻 -- "이 값은 저 값에서 계산된다", "이 값은 저 목록의 식별자다" -- 를 적는다. 관계는 한 번만, 주어 쪽에 적는다.
"total": number, // 합계 금액
derived-from items[*].price // 담은 상품 값의 합
derived-from discount.amount when discount.kind == "fixed" // 정액 할인을 뺀다| 조각 | 뜻 |
|---|---|
| 줄의 꼴 | [.sub] 낱말 주소 [when 조건] // note -- 키(또는 갈래 · 값) 아래에 들여 쓴다 |
| 주어 | 바로 위의 선언. .sub 가 있으면 그 아래 키로 좁힌다 (.carrier refers-to catalog.carriers -- 공유 def 를 쓰는 자리에서 안쪽 키의 관계를 적을 때) |
| 낱말 | 아래 열하나 -- jxn 이 준다 |
| 과녁 | 주소 (2-12) |
when |
이 관계가 서는 조건 (2-13). 갈래 안이면 갈래의 조건이 저절로 더해진다 |
| note | 줄 끝 // -- 그 관계의 풀이 |
| 낱말 | 문장 틀 | 거꾸로 읽기 (과녁 쪽) |
|---|---|---|
equals |
주어는 과녁과 같다 | 같다 |
member-of |
주어(값)는 과녁(배열)의 원소 · 주어(배열)는 과녁의 부분 | 담는다 |
refers-to |
주어의 값이 과녁의 식별자다 | 가리켜진다 |
follows |
주어(원소)는 과녁 바로 뒤에 붙는다 | 앞에 선다 |
copies |
과녁의 값을 모양만 바꿔 옮겨 온다 | 옮겨 간다 |
derived-from |
과녁을 재료로 계산된다 | 재료다 |
sets · clears |
주어가 붙으면 과녁 상태가 켜진다 · 꺼진다 | 켜진다 · 꺼진다 |
adds-to · removes-from |
주어(가 가리키는 원소)가 과녁 모음(배열 · map)에 들어간다 · 빠진다 | 넣어진다 · 빠진다 |
reason-of |
주어는 과녁의 까닭이다 | 까닭을 가진다 |
sets·clears는 켜고 끄는 상태에만, 모음에 넣고 빼는 것은adds-to·removes-from으로.- 한 번만 적는다 -- 주어 쪽에. 과녁 쪽의 거꾸로 읽기는
jxn doc이 보인다. - 기계로 볼 수 있는 관계는
jxn check가 검사한다 (과녁이 인스턴스 안에 있을 때). - JSON Schema:
x-relations: [{sub?, rel, key, when?, note}].
2-12. 주소#
주소 := "$" 경로 | 앵커 [ 경로 ] | 이름 경로 $ 는 뿌리, 앵커가 없으면 home 에서
경로 := ( "." 이름 | "[*]" | ".*" )* [*] = 배열의 모든 원소 · .* = map 의 모든 값
앵커 := 이름 [ "[" ("+n" | "-n" | "*" | "..-n") "]" ]선언:
home $.history[*] // 앵커 없는 주소 = 주어가 든 이력 원소에서
anchor history = $.history // history[+1] · history[-1] = 바로 옆 원소
anchor change = $.history : Change // change[-1] = 바로 앞 Change (다른 갈래는 건너뛴다)
anchor catalog external // 인스턴스 밖 -- 검사하려면 함께 받아야 한다- 뿌리
$· 모든 원소[*]· 모든 값.*는 JSONPath 와 같다. - 앵커에 붙는
[+n][-n]= 지금 원소에서 n 번째 뒤 · 앞 (!) JSONPath 의[-1]은 "마지막 원소" 라 뜻이 다르다.[*]= 아무 원소 (refers-to 의 과녁).[..-n]= n 번째 앞까지의 모든 원소 (범위). 이름 : Def= 배열 원소 가운데 그 갈래만 센다.home이 없으면 home 은 뿌리다. 앵커 · home 의 주소는$에서 시작한다.- 주소의 첫 이름이 앵커 이름이면 앵커다 -- 같은 이름의 키보다 앞선다.
external앵커는 인스턴스에 없는 것(참조 데이터 · 계산으로만 있는 상태)이다. 그 관계는 그것을 함께 받아야 검사된다.- 앵커 없는 주소는 쓰는 자리마다 푼다. def 안의 관계라면 도구는 그 def 를 쓰는 자리마다 home 에서 주소를 풀어 보고, 한 곳이라도 안 풀리면 오류다.
이름[주소](대괄호 안에 주소 -- 값으로 찾기)는 오류다. 외부 조인을 열 자리로 남겨 둔다 (5절).
2-13. 조건 when#
when := 절 (" and " 절)*
절 := 주소 op 값
op := "==" | "!=" | "in"
값 := JSON 값 | enum def 이름 (in 의 오른쪽)예: when method == "card" · when state in ["paid", "shipped"] · when method in Method.
2-14. 이어받기#
inherit주석은 가장 가까운 조상에서 온다. 조상 = 감싼 object · 배열 · 갈래 · 쓰는 자리.- 우선순위: def 안쪽 키에 적은 값 > 쓰는 자리에 적은 값 > def 머리에 적은 값 > 쓰는 자리가 조상에서 이어받은 값. "쓰는 자리" 는 def 를 곧장 타입으로 쓰는 키 · 갈래다 (
"k": D·"k": null | D·"k": [ D ]·"k": { [..]: D }·"k": expect D·| "v" => D). 배열의 합([ A | B ])에서는 원소마다 제 def 머리가 선다. - 그러니 def 머리의 값은 조상의 값보다 가깝다 -- def 를 어디서 쓰든 따라간다. 쓰는 자리가 적어서 덮을 때만 진다.
- description · 풀이와
inherit가 없는 주석(status등)은 이어받지 않는다. 단, 타입이 def 하나뿐인 키에//뜻이 없으면 그 def 의 description 을 쓴다. - "이 아래 일부만" 은 부모에 값을 달고 아래 키가 덮어 적는다.
jxn doc은 아래 키의 값이 갈리는 자리를 보인다.
2-15. 상태 status -- jxn 이 준다#
| 값 | 뜻 |
|---|---|
stable |
정함 -- 뜻이 있고, 모양이 닫혔고, 정한 까닭이 있다 |
draft |
초안 -- 키는 있으나 뜻이 아직 무르거나, 안쪽 모양 · 자리가 뒤로 미뤄졌다 |
stub |
모름 -- 이름만 있다. 뜻(//)이 없어도 된다 |
- 정의 자체의 상태라 도메인과 상관없이 jxn 이 준다. 나머지 주석(누가 채우나 · 누가 읽나 · 출처 · 짧은 풀이 ...)은 모두 파일이 선언한다.
- 이어받지 않는다. 기본은 선택이고,
tag status need한 줄로 모든 키에 요구한다. 기본값은 없다 -- 적지 않은 것은 "없음" 으로 보인다. jxn doc: 트리 배지 · 점검 칸(없음 · draft · stub 인 키). 화면에서는 정함 · 초안 · 모름으로 보여도 된다.- JSON Schema:
x-jxn-status.
2-16. 글 속의 링크와 키 참조 (0.3)#
"total": number, // 합계 금액 -- [계산 규칙](docs/계산.md)
@src [주문 규격 4절](docs/주문.md), https://example.com/order-spec
// items[*].price 를 더하고, 할인(discount)이 정액이면 discount.amount 를 뺀다바깥 문서 -- 링크. 글 안의 [글](주소) (Markdown 링크)와 맨 http:// · https:// 주소는 링크다.
- 자리: 줄 끝
//뜻 ·//풀이 ·// ?물음 ·// #머리의 풀이 · 값 목록 · 갈래 · 목록 값의 뜻 · 관계의 note ·text·set<text>꼴 주석의 값. 키 이름 · 타입 · 주소 · 허용 값 · 목록 꼴 주석 값에는 쓰지 않는다. [글]안에는]를,(주소)안에는 공백과)를 쓰지 않는다 (공백은%20).[글]과(주소)사이를 띄우면 링크가 아니다. 링크가 아닌[]()는 그냥 글이다.- 상대 주소는 .jxn 파일이 있는 폴더에서 푼다.
http:·https:·mailto:같은 주소와#...는 그대로다. jxn doc페이지에서 누르면 보통 링크처럼 그 주소로 간다 (같은 탭). 상대 주소는 페이지를 낸 자리에 맞게 고쳐 단다. 로컬 파일은 열리기만 한다 -- 파일의 줄까지 가지 않는다.
문서 안 -- 키 참조는 저절로. 글 안에서 jxn 주소 꼴(in.body · step[+1].in.from · $.orders)이나 낱말 하나(event)가 그 자리에서 키로 풀리면 키 참조다. 쓰는 사람이 표지를 달지 않는다.
- 주소 꼴은 관계 주소와 같은 규칙으로 푼다 (2-12 -- 앵커, 없으면 home). def 안의 글이면 그 def 를 쓰는 자리마다 따로 푼다.
- 낱말 하나는 가까운 곳에서 찾는다 -- 그 선언의 아래 키, 형제 키, home 의 키 차례. 앵커 이름만 홀로 있으면 참조로 보지 않는다 (배열 전체인지 그 안의 키인지 갈리지 않는다).
- 풀리지 않으면 그냥 글이다 -- 경고하지 않는다. 링크 안의 글은 키 참조로 보지 않는다.
jxn doc페이지에서 누르면 같은 페이지에서 그 키의 설명이 열린다 (주소창의#<키 id>가 바뀐다 -- 뒤로 가기로 돌아온다).- 백틱
`x`는 고정폭 글자로 보일 뿐이다 -- 그 안의 글도 풀리면 키 참조다.
JSON Schema 에는 글이 적힌 대로 나간다 ([글](주소) 그대로 -- Markdown 을 읽는 도구는 링크로 보인다).
3. 한 표로 -- JSON Schema 로#
| jxn | JSON Schema 2020-12 |
|---|---|
뿌리 { } |
문서 뿌리 + title (파일 이름) |
def N |
$defs/N · 쓰는 곳 $ref |
"key": T / "key"?: T |
properties.key + required / required 에서 뺀다 |
{ ... } |
type: object, additionalProperties: false |
{ ... } + 갈래 · 펼치기 |
unevaluatedProperties: false + allOf |
...N |
N 의 properties · required · allOf 를 풀어 넣는다 |
by k + | v => T (필드) |
allOf: [{if: {k in [v]}, then: {key: T, required}}] · absent -> {key: false} |
by k + | v => {...} (object) |
allOf: [{if: {k in [v]}, then: {properties}}] |
A | B |
enum (모두 값) / anyOf |
값 목록 | v // 뜻 @t x |
enum + x-jxn-values |
[ T ] |
type: array, items: T |
{ [뜻]: T } |
additionalProperties: T + x-jxn-key |
unknown / any |
{x-jxn-unknown: true} / {} |
expect T |
x-jxn-expect: T (표준 검사기는 보지 않는다) |
T min n |
minimum / minItems / minLength / minProperties |
줄 끝 // 뜻 |
description (링크 [글](주소) 는 적힌 대로) |
| 말 줄 (풀이) | x-jxn-notes: [...] |
@status v |
x-jxn-status |
@t v (파일이 선언한 주석) |
x-t |
| 관계 | x-relations |
// ? · // # |
(나가지 않는다 -- jxn doc 의 열린 물음 목록 · 무리) |
jxn 은 인스턴스에서 아무것도 추론하지 않는다 -- required 도 적힌 대로다. 예시 JSON 은 jxn doc 이 보여 주기에만 쓴다.
4. 오류#
아래는 틀린 파일이다 -- 도구는 JSON Schema 를 내지 않는다. 괄호 안은 참조 구현(jxn build)의 진단 코드다.
| 무엇 | 오류 |
|---|---|
첫 코드 줄이 jxn 0.3 (또는 jxn 0.2) 가 아니다 |
E_HEADER |
0.1 파일이다 (jxn 0.1) -- jxn upgrade 로 옮긴다 |
E_VERSION |
읽을 수 없는 줄 · 타입식 · 주소 · 조건, 따옴표 없는 키 · 값, 붙을 선언이 없는 주석 · 관계 줄, 이름[주소], [] (0.2 는 [*]) |
E_SYNTAX |
| 닫히지 않은 블록 | E_UNCLOSED |
| 뿌리가 없다 · 둘이다 | E_NO_ROOT · E_ROOT_DUP |
| 같은 이름의 def · list · tag · anchor, 한 object 의 같은 키, 한 목록의 같은 값 | E_DUP · E_DUP_KEY · E_DUP_VALUE |
| 없는 def · 목록을 부른다, object def 가 아닌 것을 펼친다 | E_UNKNOWN_DEF · E_UNKNOWN_LIST · E_SPREAD |
status 를 목록 · 타입으로 선언한다, status 를 이어받게 한다 |
E_STATUS_BUILTIN |
| 값의 꼴이 없는 tag, 선언하지 않은 주석, 꼴에 맞지 않는 주석 값 | E_TAG_NOSPEC · E_TAG_UNKNOWN · E_TAG_VALUE |
need 인 주석이 이어받기를 푼 뒤에도 없는 키 |
E_NEED |
| 갈래가 판별 키의 값을 덮지 않는다 · 남는다 · 겹친다, 판별 키가 없거나 값 목록이 아니다 | E_BY_MISSING · E_BY_EXTRA · E_BY_DUP · E_BY_KEY |
필드 갈래가 아닌 곳의 absent, absent 갈래가 있는데 ? 가 없는 키, object 가 아닌 object 갈래 |
E_ABSENT_PLACE · E_ABSENT_REQUIRED · E_BY_OBJECT |
| 가르는 "반드시 있는 키" 가 없는 object 의 합 | E_UNION_AMBIGUOUS |
풀리지 않는 앵커 · home, 쓰는 자리에서 풀리지 않는 관계 · 조건의 주소, 값 목록이 아닌 in Def |
E_ANCHOR · E_ADDR · E_WHEN_ENUM |
경고 -- 파일은 옳고 도구가 알린다.
| 무엇 | 경고 |
|---|---|
| 판이 0.2 · 0.3 이 아니다 (0.1 은 오류) | W_VERSION |
주석 줄 끝의 // 글 (버린다) |
W_DESC_IGNORED |
| 한 선언에 같은 주석을 두 번 (뒤의 것이 이긴다) | W_TAG_DUP |
| 값에 단 관계 (아직 검사하지 않는다) | W_REL_ON_VALUE |
5. 0.3 에 없는 것#
쓰는 곳이 없거나 한 곳이다 -- 쓰임이 요구할 때 연다 (INTENT 7절).
need에 범위 (need in Def.*)- 범위 주소
[..-n]에 조건 - 외부 조인 (
when catalog[item.sku].kind == ...) --이름[주소]는 그때까지 오류다 - map 의 키가 def 를 가리킬 때
propertyNames - 갈래 이름표에 enum def 이름 (그 값 모두)
- 여러 줄
|로 쓰는 타입의 합 -- 여러 줄|는 값 목록이다. 타입의 합은 한 줄에A | B - 문자열 모양 (
format·pattern) · 상태deprecated - home 여럿 · "제 원소" 주소 -- home 밖 배열의 원소 안에서 제 이웃 키를 가리키는 길
- 관계 낱말 다시 짜기 -- 첫 사용처의 쓰임을 더 보고 연다
- 글 속의 Markdown 꾸밈 (굵게 · 기울임 · 목록) -- 링크만 있다
- 로컬 파일의 줄로 가는 링크 (브라우저에 줄 주소가 없다)
6. 옛 판에서 옮기기#
jxn upgrade FILE.jxn --write # 0.1 · 0.2 파일을 0.3 으로 덮어쓴다 (-o OUT.jxn 이면 다른 파일로, 없으면 stdout)- 0.2 -> 0.3 -- 머리만 바뀐다 (0.3 은 더하기만 했다). 옮기지 않아도 0.2 파일은 그대로 읽힌다.
- 0.1 -> 0.3 -- 0.1 로 읽고, 주소를 옮기고 (뿌리 이름 ->
$·[]->[*]), 0.2 의 꼴로 다시 쓴다. - 다시 쓴 글을 새 판으로 다시 읽어 오류가 없을 때만 쓴다. 뜻 · 주석 · 관계 · 말 · 물음 · 머리와 그 차례, 빈 줄 자리는 남는다. 꾸밈 줄(
-- ====)은 없어진다. 뜻의 칸은 한 줄로 맞춰진다. - 파일의 모양만 맞추려면
jxn fmt FILE.jxn --write(판은 그대로 둔다).