-
Jsonnet 톺아보기programing/Language 2021. 5. 19. 15:00
Jsonnet 이란?
A data templating language for app and tool developers
Jsonnet은 JSON과 sonnet의 합성어로, 데이터 템플레이팅 언어입니다. 기존의 데이터 구조화에 사용되던 언어인 JSON의 확장판이라고 보시면 됩니다.
JSON에 프로그래밍의 일부 기술을 접목했다고 보시면 될 것 같습니다.
기본적으로는 C++로 구현되어 있지만, 현재 Google이 관리하고 있어서 그런지 GO로 구현된 go-jsonnet도 있습니다.
현재 버전은 v.0.17.0이지만, 태그 날짜를 보니 활발히 개발되고 있는 듯 합니다.
참고로 발음은 "jay sonnet"이라고 합니다.
철학
Jsonnet은 각기 다른 환경을 하나로 통합하는 것을 목표로 개발되었습니다. 각각의 구성을 독립적으로 구성하면 중복 및 유지보수가 어려워지기 때문입니다.
만약 당신이 응용프로그램 개발자라면 애플리케이션이 JSON 혹은 구조화된 포멧을 사용하도록 하면 됩니다. 사용자는 Jsonnet을 이용해 자신만의 설정 파일을 생성하면 됩니다.
Jsonnet은 풀 프로그래밍 언어이므로, 어떠한 로직도 자유롭게 구현할 수 있습니다. 다만, 제한없는 IO는 허용하지 않으므로, 입출력이 명확하고 한정적인 경우에 사용하는 것이 좋습니다. 구성이라기 보다, 어플리케이션으로 사용되는 몇가지 사례는 다음과 같습니다. (그런데 저는 잘 이해가 안되네요.. 🤔)
- 정적 사이트 생성
- 임베딩된 표현 언어
- Ad hoc JSON transformations (뭔말이야??)
- 교육
특징
- Generate config data : 설정 파일을 생성하는데 용이합니다. (애초에 데이터 템플레이팅 언어니까요..)
- Side-effect free : 사이드 이펙트가 없습니다. (Jsonnet이 함수형 언어이기 때문입니다.. 만 구현에 따라서 충분히 사이드 이펙트가 있을 수 있습니다.)
- Organize, simplify, unify : 구조화, 단순화, 통일성
- Manage sprawling config : 무분별한 설정들을 관리하기 용이합니다.
Functional Language
Jsonnet은 OO를 포함한 함수형 언어이기 때문에, 기존의 명령형 언어에 익숙하다면 조금 낯설 수 있습니다. 즉, 타 언어가 서술적이라면 함수형 언어는 선언적(무엇을 할 지)이기 때문입니다. 이러한 특성 덕분에 사용자는 상태와 명령의 순서에 대해 고민할 필요가 없습니다. 😎
Extension of JSON
- Open source (Apache 2.0) : 비교적 자유로운 아파치 라이센스 오픈 소스입니다.
- Familiar syntax : JS 및 phthon과 유사한 문법을 가지고 있습니다.
- Reformatter, linter : 리포맷 및 린트를 적용가능한 듯 합니다.
- Editor & IDE integrations : 에디터 지원이 된다고 합니다.
- Formally specified : 공식적으로 지정된..? 잘 모르겠네요. 😅
위에 적힌 것들 외에도 Computed Field Name, Python 스타일 배열 및 객체, 객체 지향(상속) 등을 지원합니다. 😮
문법
기본
- 유효한 식별자(아마도 스코프 내에서 유일한 값)인 필드는 따옴표가 필요없습니다.
- Trailing comma는 필수입니다.
- 주석을 지원합니다. (
/* */
및#
) - 스트링 리터럴은
"
과'
둘 다 지원합니다. - python과 유사하게,
|||
를 이용하면 멀티 라인 스트링을 작성할 수 있습니다. - 단일 라인은
@'foo'
로 작성할 수 있습니다.
변수
local
키워드를 통해 변수를 선언 및 할당합니다.- 필드와 같은 레벨에 선언할 수 있으며, 이 때는 JS처럼
,
로 끝냅니다. - 그 외의 경우에는 항상
;
로 끝냅니다.
// A regular definition. local house_rum = 'Banks Rum'; { // A definition next to fields. local pour = 1.5, Daiquiri: { ingredients: [ { kind: house_rum, qty: pour }, { kind: 'Lime', qty: 1 }, { kind: 'Simple Syrup', qty: 0.5 }, ], served: 'Straight Up', }, }
{ "Daiquiri": { "ingredients": [ { "kind": "Banks Rum", "qty": 1.5 }, { "kind": "Lime", "qty": 1 }, { "kind": "Simple Syrup", "qty": 0.5 } ], "served": "Straight Up" } }
house_rum
과pour
변수를 활용한 모습입니다.레퍼런스
self
는 현재 객체를 가리킵니다.$
는 가장 바깥의 객체를 가리킵니다.['some_filed']
는some_filed
라는 이름의 필드를 찾습니다.- JS의 객체와 같이,
.
을 이용해 특정 객체의 필드에 접근할 수 있습니다. - 배열과 같이,
[]
를 이용해 특정 인덱스 요소를 참조할 수 있으며, python 처럼 슬라이싱이 가능합니다. - Arbitrarily long paths are allowed.
- Array slices like
arr[10:20:2]
are allowed, like in Python. - Strings can be looked up / sliced too, by unicode codepoint.
연산자
서로 다른 타입의 값을 연산할 수 있습니다.
- 부동소수점 산술, 비트 연산, 불 논리를 사용합니다.
- JS처럼
+
를 이용해 문자열 연산을 할 수 있습니다. (임의의 타입 변경도 포함) - 두 문자열은
<
를 이용해 비교할 수 있습니다. (유니코드 순서). +
를 이용해 다른 객체를 상속할 수 있습니다. 단, 충돌나는 필드는 오른쪽 객체로 오버라이딩 합니다.- phython처럼, 객체에 필드의 유뮤를 판단할 때
in
을 사용합니다. - 깊은 비교를 할 땐
==
를 사용합니다. - python의 옛날 문법인 %-포매팅을 지원합니다.
{ concat_array: [1, 2, 3] + [4], concat_string: '123' + 4, equality1: 1 == '1', equality2: [{}, { x: 3 - 1 }] == [{}, { x: 2 }], ex1: 1 + 2 * 3 / (4 + 5), // Bitwise operations first cast to int. ex2: self.ex1 | 3, // Modulo operator. ex3: self.ex1 % 2, // Boolean logic ex4: (4 > 3) && (1 <= 3) || false, // Mixing objects together obj: { a: 1, b: 2 } + { b: 3, c: 4 }, // Test if a field is in an object obj_member: 'foo' in { foo: 1 }, // String formatting str1: 'The value of self.ex2 is ' + self.ex2 + '.', str2: 'The value of self.ex2 is %g.' % self.ex2, str3: 'ex1=%0.2f, ex2=%0.2f' % [self.ex1, self.ex2], // By passing self, we allow ex1 and ex2 to // be extracted internally. str4: 'ex1=%(ex1)0.2f, ex2=%(ex2)0.2f' % self, // Do textual templating of entire files: str5: ||| ex1=%(ex1)0.2f ex2=%(ex2)0.2f ||| % self, }
{ "concat_array": [ 1, 2, 3, 4 ], "concat_string": "1234", "equality1": false, "equality2": true, "ex1": 1.6666666666666665, "ex2": 3, "ex3": 1.6666666666666665, "ex4": true, "obj": { "a": 1, "b": 3, "c": 4 }, "obj_member": true, "str1": "The value of self.ex2 is 3.", "str2": "The value of self.ex2 is 3.", "str3": "ex1=1.67, ex2=3.00", "str4": "ex1=1.67, ex2=3.00", "str5": "ex1=1.67\nex2=3.00\n" }
함수
python처럼 위치 파라미터, 이름있는 파라미터, 기본 값을 지원합니다. 또한 JS처럼 클로저를 지원하며, 함수는 일급 객체입니다.
// A function that returns an object. local Person(name='Alice') = { name: name, welcome: 'Hello ' + name + '!', }; { person1: Person(), person2: Person('Bob'), }
{ "person1": { "name": "Alice", "welcome": "Hello Alice!" }, "person2": { "name": "Bob", "welcome": "Hello Bob!" } }
순수 함수
Person
을 이용해서 특정 로직을 캡슐화 및 재사용 할 수 있습니다.포멧 변환
local application = 'my-app'; local module = 'uwsgi_module'; local dir = '/var/www'; local permission = 644; { 'uwsgi.ini': std.manifestIni({ sections: { uwsgi: { module: module, pythonpath: dir, socket: dir + '/uwsgi.sock', 'chmod-socket': permission, callable: application, logto: '/var/log/uwsgi/uwsgi.log', }, }, }), 'init.sh': ||| #!/bin/bash mkdir -p %(dir)s touch %(dir)s/initialized chmod %(perm)d %(dir)s/initialized ||| % {dir: dir, perm: permission}, 'cassandra.conf': std.manifestYamlDoc({ cluster_name: application, seed_provider: [ { class_name: 'SimpleSeedProvider', parameters: [{ seeds: '127.0.0.1' }], }, ], }), }
// cassandra.conf "cluster_name": "my-app" "seed_provider": - "class_name": "SimpleSeedProvider" "parameters": - "seeds": "127.0.0.1" // init.sh #!/bin/bash mkdir -p /var/www touch /var/www/initialized chmod 644 /var/www/initialized // uwsgi.ini [uwsgi] callable = my-app chmod-socket = 644 logto = /var/log/uwsgi/uwsgi.log module = uwsgi_module pythonpath = /var/www socket = /var/www/uwsgi.sock
JSON을 포함하여 YAML, INI 등 다양한 형식으로의 변환을 지원합니다.
기타
Standard Library를 통해 여러가지 부가 기능을 제공하고 있습니다.
- 외부 변수
- 리플렉션
- 연산 유틸 함수
- 테스트 및 디버깅
- 문자열 연산
- 파싱
설치
brew install jsonnet
pip install jsonnet
혹은 GCC로도 빌드가 가능합니다.
사용법
인터프리터
가장 간단한 방법은, phython처럼 인터프리터를 이용해 jsonnet 파일을 데이터 포메팅 파일로 변환할 수 있습니다.
jsonnet -e <jsonnet_code>
jsonnet <file_name> > <output_name>
활용
제 경우에는 그렇게 복잡한 설정 파일을 만들 일이 별로 없다 보니, 실용성은 아직까지는 크게 와닿지 않습니다..만, 다음과 같은 경우에 유용하다고 합니다.
- 기본 설정을 여러 환경이 개별적으로 확장해서 사용할 때
// base.libsonnet { api: { timeout: 10, } } // staging.jsonnet import 'base.libsonnet' // production.jsonnet (import 'base.libsonnet') + { rack_timeout: 10 }
- 외부로부터 설정 값을 받고 싶을 때
// config.jsonnet { secret_key: std.extVar("MY_SECRET_KEY") } // command $ jsonnet config.jsonnet --ext-str MY_SECRET_KEY=foobarbaz
찍먹
// fe_study.jsonnet local study_name = 'FE_STUDY'; local study_participant = ['YS', 'HJ', 'TH', 'TE', null]; local makeInfo(name) = { local privateField = "you can't see me, %s", name: name, age: 20, isPrivate: if name == null then privateField % name else 'hello~', }; { name : study_name, name : 'foo', participants: [makeInfo(participant) for participant in study_participant] }
# 문법 에러가 있는 경우 $ jsonnet fe_study.jsonnet STATIC ERROR: fe_study.jsonnet:10:5-9: duplicate field: name
# 정상적으로 인터프리팅이 된 경우 { "name": "FE_STUDY", "participants": [ { "age": 20, "isPrivate": "hello~", "name": "YS" }, { "age": 20, "isPrivate": "hello~", "name": "HJ" }, { "age": 20, "isPrivate": "hello~", "name": "TH" }, { "age": 20, "isPrivate": "hello~", "name": "TE" }, { "age": 20, "isPrivate": "you can't see me, null", "name": null } ] }
참고
'programing > Language' 카테고리의 다른 글
[CSS] 변수합성을 통한 테마 구현하기 (0) 2021.07.03 [Shell Script] 정규표현식을 이용해 파일명 변경(치환)하기 (0) 2021.02.13 [JavaScript] 정규표현식으로 문자열 쪼개기 (0) 2020.11.22 [JavaScript] 네이티브 자바스크립트로 range 구현하기 (0) 2020.11.22 [JavaScript] yeild* (1) 2020.10.25 댓글