You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
JsonDsl is a multi platform kotlin library that helps you build Kotlin DSLs for JSON and YAML dialects.
6
-
The DSLs are easy to extend with custom fields by users via a MutableMap.
6
+
DSLs made with this library are easy to extend with custom fields by users via a MutableMap.
7
7
8
-
A DSL (Domain Specific Language) differs from General Purpose Languages, such as Kotlin, in that a DSL is intended to program or drive a tool or framework for some domain or API. Kotlin like several other languages is suitable for creating internal DSLs that (ab)use the syntax of the host language to implement a DSL.
8
+
# About DSLs
9
9
10
-
## The problem
10
+
A Domain Specific Language (DSL) is a language that is intended to allow users to program or specify things in language that closely matches their domain. They are popular for configuration files, for use with certain frameworks or tools, and there are a lot of niche tools, frameworks, and other software packages out there that implement them.
11
11
12
-
Of course, creating model classes for your json domain model and annotating them with annotations for e.g. `kotlinx.serialization` is a valid way to start creating a DSL for your JSON or YAML dialect of choice.
12
+
Some general purpose languages have a syntax that makes it easy to (ab)use their features to do something similar. Lisp and Ruby are a good example of languages that have historically been used for this. Like those languages, Kotlin includes a few features that enable this. Which makes Kotlin very well suited for implementing all sorts of DSLs.
13
13
14
-
However, this has some limitations. What if your JSON dialect evolves and somebody adds some new features? Unless you change your model class, it would not be possible to access such new features via the Kotlin DSL. Or what if your JSON dialect is vast and complicated. Do you have to support all of it? How do you decide what to allow and not allow in your Kotlin DSL.
14
+
There are Kotlin DSLs for all sorts of things. A popular example is the HTML DSL that comes with things like Ktor and kotlin-js. Spring bundles a lot of Kotlin DSLs for it's Java framework that make using that a lot nicer than from Java. There are lots of examples.
15
15
16
-
This library started out as few classes in my [kt-search](https://github.com/jillesvangurp/kt-search) project, which implements an Elasticsearch and Opensearch client. Elasticsearch has several JSON dialects that are used for querying, defining index mappings, settings, and a few other things. Especially the query language has a large number of features and is constantly evolving.
16
+
## Making easy to extend Kotlin DSLs for JSON/YAML dialects
17
+
18
+
Of course, creating model classes for your JSON domain model and annotating thosre with annotations for e.g. `kotlinx.serialization`, `jackson`, etc. is a perfectly valid way to start creating a DSL for your JSON or YAML dialect of choice.
19
+
20
+
However, using JSON frameworks like that have some limitations. What if your JSON dialect evolves and somebody adds some new features? Unless you change your model class, it would not be possible to access such new features via the Kotlin DSL. Or what if your JSON dialect is vast and complicated. Do you have to support all of its features? How do you decide what to allow and not allow in your Kotlin DSL.
21
+
22
+
This library started out as few classes in my [kt-search](https://github.com/jillesvangurp/kt-search) project. Kt-search implements a client library for Elasticsearch and Opensearch. Elasticsearch has several JSON dialects that are used for querying, defining index mappings, settings, and a few other things. Especially the query language has a large number of features and is constantly evolving.
17
23
18
24
Not only do I have to worry about implementing each and every little feature these DSLs have and keeping up with upstream additions to OpenSearch and Elasticsearch. I also have to worry about supporting query and mapping features added via custom plugins. This is very challenging. And it was the main reason I created json-dsl: so I don't have to keep up.
19
25
20
26
## Strongly typed and Flexible
21
27
22
-
The key feature in json-dsl is that it uses a `MutableMap` for storing property values. This enables you to define classes with properties that delegate storing their value to this map. For anything that your
23
-
classes don't implement, the user can always write to the map directly using a simple `put`.
28
+
The key feature in json-dsl is that it uses a `MutableMap`and property delegation for implementing DSL classes. This simple approach enables you to define classes with properties that delegate storing their value to this map. For anything that your
29
+
classes don't implement, the user can simply modify the underlying map directly using a simple `put`.
24
30
25
-
This gives users a nice fallback for things your DSL classes don't implement and it relieves Kotlin DSL implementors from having to provide support for every new feature the upstream JSON dialect has or adds over time. You can provide a decent experience for your users with minimal effort. And you users can always work around whatever you did not implement.
31
+
This simple approach gives users a nice fallback for things your DSL classes don't implement and it relieves Kotlin DSL creators from having to provide support for every new feature the upstream JSON dialect has or adds over time. You can provide a decent experience for your users with minimal effort. And you users can always work around whatever you did not implement.
26
32
27
33
With kt-search, I simply focus on supporting all the commonly used, and some less commonly used things in the Elastic DSLs. But for everything else, I just rely on letting the user modify the underlying map themselves. A lot of pull requests I get on this project are people adding features they need in the DSLs. So, over time, feature support has gotten more comprehensive.
28
34
@@ -49,7 +55,7 @@ And then you can add the dependency:
If you were using json-dsl via kt-search before, you can update simply by bumping the version of json-dsl to 3.0. 2.x got released along with kt-search and has now been removed.
58
+
If you were using json-dsl via kt-search before, you can update simply by bumping the version of json-dsl to 3.0. Previously, 2.x was released along with kt-search and has now been removed from that project.
53
59
54
60
55
61
## Examples
@@ -97,13 +103,11 @@ There is also a YAML serializer. More on that below.
97
103
### Common Kotlin Types
98
104
99
105
JSON is a fairly simple data format. It has numbers, booleans, strings, lists and dictionaries. And null
100
-
values.
101
-
102
-
Kotlin has a bit richer type system and mapping that to JSON is key to providing rich Kotlin DSL.
106
+
values. Kotlin has a bit richer type system and mapping that to JSON is key to providing rich Kotlin DSL.
103
107
104
108
JsonDsl does a best effort to do map Kotlin types correctly to the intended JSON equivalent.
105
109
106
-
It understands all the primitives, Maps and Lists. But also Arrays, Sets, Sequences.
110
+
It understands all the primitives, Maps and Lists. But also Arrays, Sets, Sequences, etc.
107
111
And of course other JsonDsl classes, so you can nest them. And it falls back to using
108
112
`toString()` for everything else.
109
113
@@ -133,14 +137,14 @@ MyDsl().apply {
133
137
idontknow =mapOf(
134
138
"arrays" to arrayOf(
135
139
1, 2, "3", 4.0,
136
-
mapOf("this" to "is valid JSON")
140
+
mapOf("this" to "is valid JSON"), "mixing types is allowed in JSON"
137
141
),
138
142
"sequences" to sequenceOf(1,"2",3.0)
139
143
)
140
144
}
141
145
```
142
146
143
-
This does the right things with all the Kotlin types, including `Any`:
147
+
This does the right things with all the used Kotlin types, including `Any`:
144
148
145
149
```json
146
150
{
@@ -166,7 +170,8 @@ This does the right things with all the Kotlin types, including `Any`:
166
170
4.0,
167
171
{
168
172
"this": "is valid JSON"
169
-
}
173
+
},
174
+
"mixing types is allowed in JSON"
170
175
],
171
176
"sequences": [
172
177
1,
@@ -192,18 +197,21 @@ MyDsl().apply {
192
197
// nicely typed.
193
198
foo ="bar"
194
199
200
+
// but we never defined a bar property
195
201
this["bar"] ="foo"
196
-
this["going_off_script"] =listOf(
202
+
// or this ...
203
+
this["whatever"] =listOf(
197
204
MyDsl().apply {
198
-
this["anything"] ="is possible"
205
+
this["you"] ="can add anything you want"
199
206
},
200
207
42
201
208
)
209
+
210
+
// RawJson is a Kotlin value class
202
211
this["inline_json"] =RawJson("""
203
212
{
204
213
"if":"you need to",
205
-
"you":"can even add json in string form",
206
-
"RawJson":"is a value class"
214
+
"you":"can even add json in string form",
207
215
}
208
216
""".trimIndent())
209
217
}
@@ -213,40 +221,41 @@ MyDsl().apply {
213
221
{
214
222
"foo": "bar",
215
223
"bar": "foo",
216
-
"going_off_script": [
224
+
"whatever": [
217
225
{
218
-
"anything": "is possible"
226
+
"you": "can add anything you want"
219
227
},
220
228
42
221
229
],
222
230
"inline_json": {
223
231
"if":"you need to",
224
-
"you":"can even add json in string form",
225
-
"RawJson":"is a value class"
232
+
"you":"can even add json in string form",
226
233
}
227
234
}
228
235
```
229
236
230
237
### snake_casing, custom names, defaults
231
238
232
-
A lot of JSON dialects use snake cased dictionary keys. Kotlin of course uses
239
+
A lot of JSON dialects use snake cased field names. Kotlin of course uses
233
240
camel case for its identifiers and it has certain things that you can't redefine.
234
-
235
241
Like the `size` property on `Map`, which is implemented by JsonDsl; or certain keywords.
@@ -560,15 +569,15 @@ reference for how to use JsonDsl.
560
569
561
570
## Multi platform
562
571
563
-
This is a Kotlin multi platform library that should work on most kotlin platforms (jvm, js, ios, android, etc). Wasm will be added later, after Kotlin 2.0 stabilizes.
572
+
This is a Kotlin multi platform library that should work on most kotlin platforms (jvm, js, ios, wasm, linux/windows/mac native, android, etc).
564
573
565
-
My intention is to keep this code very portable and not introduce any dependencies other than the Kotlin standard library.
574
+
My intention is to keep this code very portable and lightweight and not introduce any dependencies other than the Kotlin standard library.
566
575
567
576
## Development and stability
568
577
569
578
Before I extracted it from there, this library was part of [kt-search](https://github.com/jillesvangurp/kt-search), which has been out there for several years and has a steadily growing user base. So, even though this library is relatively new, the code base has been stable and actively used for several years.
570
579
571
-
Other than cleaning the code a bit up for public use, there were no compatibility breaking changes. This means I want to keep the API for json-dsl stable and will not make any major changes unless there is a really good reason.
580
+
Other than cleaning the code a bit up for public use, there were no compatibility breaking changes. I want to keep the API for json-dsl stable and will not make any major changes unless there is a really good reason.
572
581
573
582
This also means there won't be a lot of commits or updates since things are stable and pretty much working as intended. And because I have a few users of kt-search, I also don't want to burden them with compatibility breaking changes. Unless somebody finds a bug or asks for reasonable changes, the only changes likely to happen will be occasional dependency updates.
0 commit comments