jxn0.1

jxn 0.1 명세

jxn(JSON eXplained Notation)은 JSON 의 형태와 뜻을 정의하는 언어(스키마 언어)다 -- 사람이 읽는 정의에 뜻과 관계를 적고, JSON Schema 로 옮긴다. 이 문서는 jxn 0.1 의 명세다. 2026-09-26 확정. 1.0 전(0.x)에는 뒤로 깨는 변경이 있을 수 있다 -- 바뀐 것은 CHANGELOG.md 에 적는다.

0. 한눈에#

파일로도 있다 -- examples/order/order.jxn (인스턴스 order.json).

jxn
jxn 0.1

-- # 목록과 주석

list owner {                         -- 닫힌 목록 (정의 파일이 스스로 선언한다)
  customer                           -- 고객이 넣은 값
  system                             -- 시스템이 계산한 값
  warehouse                          -- 창고 시스템이 돌려준 원문
}
tag owner  : set<owner>  inherit need    -- @owner 주석. 조상에서 이어받고, 모든 키에 있어야 한다
tag status need                          -- jxn 이 주는 status 를 모든 키에 요구한다

anchor change  = order.history : Change  -- 이웃 주소 change[-1] = 바로 앞 Change (Note 는 건너뛴다)
anchor catalog external                  -- 인스턴스 밖의 것

-- # 주문

root order {                         -- 주문 하나
  @owner system  @status stable
  id: string                         -- 주문 번호
    @status stable
  items: array<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: array<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

1. 파일과 줄 -- 문법은 줄 단위다#

줄 꼴 뜻
머리 jxn 0.1 표기의 판. 첫 코드 줄이다
선언 key: T · key?: T · def N ... · root N { · list N { · tag N ... · anchor N ... · home 주소 · 갈래 v => T · 값 이름이 붙는 자리
주석 @tag 값[, 값] [@tag 값 ...] 바로 앞 선언에 붙는다. 한 줄에 여럿
관계 [.sub] rel 주소 [when 조건] 바로 앞 선언이 주어다
펼치기 ...Name Name 의 키를 이 object 에 펼친다
닫기 } · }> 블록을 닫는다. 뒤의 글자는 열 때의 타입식을 마저 닫는다 (array<{ ... }>)
말 -- 글 (줄 전체) 풀이. 바로 앞 선언(또는 머리)에 붙는다
머리 -- # 글 (줄 전체) 무리를 가른다 -- 같은 블록에서 다음 머리까지의 형제를 묶는다. 맨 위 수준이면 절
물음 -- ? 글 (줄 전체) 열린 물음. 바로 앞 선언에 붙고, 도구가 목록으로 모은다
꾸밈 -- ==== 처럼 - = 로만 된 줄 버린다

2. 구성 -- 꼴 · 뜻 · 예 · JSON Schema#

2-1. 닫힌 목록 list#

text
list owner {
  customer        -- 고객이 넣은 값
  "external api"  -- 공백이 있으면 따옴표
  0               -- 수도 된다
}

2-2. 주석 선언 tag#

text
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)

2-3. 정의 root · def#

text
root order { ... }                 -- 인스턴스의 뿌리 -- 파일에 하나
def Item { ... }                   -- 이름 붙은 object
def Method = "card" | "cash"       -- 이름 붙은 타입식
def Money = number min 0           -- 주석 · 관계 줄이 뒤따를 수 있다
  @status stable

2-4. 키 -- 늘 있다 · 없을 수 있다 · null 일 수 있다#

text
id: string                  -- 늘 있다                  -> required
note?: string               -- 없을 수 있다             -> required 에 넣지 않는다
coupon: null | Coupon       -- 늘 있되 null 일 수 있다  -> required + anyOf [null, ...]
"ship to": Address          -- 낱말 꼴이 아닌 키는 따옴표

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)
array<T> 배열 type: array, items
map<키의 뜻 -> T> 키가 열린 object. 키의 뜻은 -> 앞의 글 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}
text
lines: array<{              -- 여러 줄 object 는 `{` 로 열고 `}>` 로 닫는다
  sku: string
  qty: integer min 1
}>
stock: map<sku -> integer>  -- 상품 코드마다 재고

2-6. 값마다 뜻을 다는 허용 값 enum { }#

text
state: enum {                    -- 주문의 단계
  @status stable
  -- 반품은 다음 판에서 정한다      (state 의 풀이 -- 블록 첫머리)
  -- # 담는 동안
  open                           -- 담는 중
  held        @since 2           -- 잠시 멈춤
  -- # 값을 치른 뒤
  paid        @since 2           -- 결제됨
    -- 부분 결제는 아직 없다        (paid 의 풀이)
  shipped     @since 3  @status draft   -- 보냄
}

2-7. 펼치기 ...Name#

text
def Address { street: string, city: string }
shipTo: {
  ...Address
  note?: string            -- Address 에 키 하나를 더한다
}

2-8. 갈래 by#

키 하나의 모양을 이웃 키가 정한다 (필드 갈래):

text
method: "card" | "cash" | "voucher"
payment?: by method {                 -- 결제 내용
  card => { last4: string }
  voucher => Voucher
  cash => absent                      -- 현금이면 payment 키가 없다
}

object 의 키 집합을 한 키가 정한다 (object 갈래):

text
def Discount {
  kind: "percent" | "fixed"
  by kind {
    percent => { rate: number }       -- 이 갈래에서만 rate 가 있다
    fixed   => { amount: number }
  }
}

2-9. 키가 있나로 갈리는 합 A | B#

text
history: array<Change | Note>     -- Change 는 from 이, Note 는 text 가 있다

2-10. 기대하는 모양 expect T#

text
reply: expect StockReply          -- 창고가 돌려준 원문. 모양이 어긋나도 그대로 적힌다
replyCheck: array<string>         -- 어긋난 칸 -- 어긋남은 여기가 말한다

2-11. 관계#

text
total: number
  derived-from items[].price              -- 담은 상품 값의 합
sku: string
  refers-to catalog.items                 -- 카탈로그에 있는 상품
from: State
  equals change[-1].to                    -- 바로 앞 변경의 도착 단계에서 출발한다
coupon?: Coupon
  adds-to cart.discounts                  -- 쿠폰을 넣으면 할인 목록에 들어간다
removed?: array<string>
  removes-from cart.items                 -- 뺀 상품 코드
paid: boolean
  sets order.locked                       -- 결제되면 주문이 잠긴다
refund?: Refund
  reason-of change[+1]  when change[+1].to == "refunded"
낱말 문장 틀 거꾸로 읽기
equals 주어는 과녁과 같다 같다
member-of 주어(값)는 과녁(배열)의 원소 · 주어(배열)는 과녁의 부분 담는다
refers-to 주어의 값이 과녁의 식별자다 가리켜진다
follows 주어(원소)는 과녁 바로 뒤에 붙는다 앞에 선다
copies 과녁의 값을 모양만 바꿔 옮겨 온다 옮겨 간다
derived-from 과녁을 재료로 계산된다 재료다
sets · clears 주어가 붙으면 과녁 상태가 켜진다 · 꺼진다 켜진다 · 꺼진다
adds-to · removes-from 주어(가 가리키는 원소)가 과녁 모음(배열 · map)에 들어간다 · 빠진다 넣어진다 · 빠진다
reason-of 주어는 과녁의 까닭이다 까닭을 가진다

2-12. 주소#

text
주소  := 앵커 [ "." 경로 ] | 경로          앵커가 없으면 home 에서
경로  := 조각 ("." 조각)*
조각  := 이름 | 이름"[]" | "*"             이름[] = 배열의 원소 · * = map 의 아무 키
앵커  := 이름 "[" ("+n" | "-n" | "*" | "..-n") "]" | 이름

선언:

text
home   order.history[]                    -- 앵커 없는 주소 = 주어가 든 이력 원소의 뿌리에서
anchor history = order.history            -- history[+1] · history[-1] = 바로 옆 원소
anchor change  = order.history : Change   -- change[-1] = 바로 앞 Change (다른 갈래는 건너뛴다)
anchor order   = order                    -- 뿌리
anchor catalog external                   -- 인스턴스 밖 -- 검사하려면 함께 받아야 한다

2-13. 조건 when#

text
when   := 절 (" and " 절)*
절     := 주소 op 값
op     := "==" | "!=" | "in"
값     := JSON 값 | enum def 이름 (in 의 오른쪽)

예: when method == "card" · when state in ["paid", "shipped"] · when method in Method.

2-14. 이어받기#

2-15. 상태 status -- jxn 이 준다#

값 뜻
stable 정함 -- 뜻이 있고, 모양이 닫혔고, 정한 까닭이 있다
draft 초안 -- 키는 있으나 뜻이 아직 무르거나, 안쪽 모양 · 자리가 뒤로 미뤄졌다
stub 모름 -- 이름만 있다. 뜻(--)이 없어도 된다

3. 한 표로 -- JSON Schema 로#

jxn JSON Schema 2020-12
root N {} 문서 뿌리 + 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
enum { v -- 뜻 @t x } enum + x-jxn-values
map<뜻 -> 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.1 이 아니다 E_HEADER
읽을 수 없는 줄 · 타입식 · 주소 · 조건, 붙을 선언이 없는 주석 · 관계 줄, 이름[주소] E_SYNTAX
닫히지 않은 블록 E_UNCLOSED
root 가 없다 · 둘이다 E_NO_ROOT · E_ROOT_DUP
같은 이름의 def · list · tag · anchor, 한 object 의 같은 키, 한 목록 · enum 의 같은 값 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 · by, absent 갈래가 있는데 ? 가 없는 키, object 가 아닌 object 갈래 E_ABSENT_PLACE · E_BY_PLACE · E_ABSENT_REQUIRED · E_BY_OBJECT
가르는 "반드시 있는 키" 가 없는 object 의 합 E_UNION_AMBIGUOUS
풀리지 않는 앵커 · home, 쓰는 자리에서 풀리지 않는 관계 · 조건의 주소, 값 목록이 아닌 in Def E_ANCHOR · E_ADDR · E_WHEN_ENUM

경고 -- 파일은 옳고 도구가 알린다.

무엇 경고
판이 0.1 이 아니다 W_VERSION
주석 줄 끝의 -- 글 (버린다) W_DESC_IGNORED
한 선언에 같은 주석을 두 번 (뒤의 것이 이긴다) W_TAG_DUP
enum 값에 단 관계 (아직 검사하지 않는다) W_REL_ON_VALUE

5. 0.1 에 없는 것#

쓰는 곳이 없거나 한 곳이다 -- 쓰임이 요구할 때 연다 (INTENT 7절). 까닭은 notes/문법-0.1-결정.md C.