Skip to content

Commit 1309907

Browse files
committed
tweak docs
1 parent 158126a commit 1309907

File tree

4 files changed

+81
-64
lines changed

4 files changed

+81
-64
lines changed

README.md

+43-34
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,32 @@
33
[![Process Pull Request](https://github.com/jillesvangurp/json-dsl/actions/workflows/pr_master.yaml/badge.svg)](https://github.com/jillesvangurp/json-dsl/actions/workflows/pr_master.yaml)
44

55
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.
77

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
99

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.
1111

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.
1313

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.
1515

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.
1723

1824
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.
1925

2026
## Strongly typed and Flexible
2127

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`.
2430

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.
2632

2733
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.
2834

@@ -49,7 +55,7 @@ And then you can add the dependency:
4955
implementation("com.jillesvangurp:json-dsl:3.x.y")
5056
```
5157

52-
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.
5359

5460

5561
## Examples
@@ -97,13 +103,11 @@ There is also a YAML serializer. More on that below.
97103
### Common Kotlin Types
98104

99105
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.
103107

104108
JsonDsl does a best effort to do map Kotlin types correctly to the intended JSON equivalent.
105109

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.
107111
And of course other JsonDsl classes, so you can nest them. And it falls back to using
108112
`toString()` for everything else.
109113

@@ -133,14 +137,14 @@ MyDsl().apply {
133137
idontknow = mapOf(
134138
"arrays" to arrayOf(
135139
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"
137141
),
138142
"sequences" to sequenceOf(1,"2",3.0)
139143
)
140144
}
141145
```
142146

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`:
144148

145149
```json
146150
{
@@ -166,7 +170,8 @@ This does the right things with all the Kotlin types, including `Any`:
166170
4.0,
167171
{
168172
"this": "is valid JSON"
169-
}
173+
},
174+
"mixing types is allowed in JSON"
170175
],
171176
"sequences": [
172177
1,
@@ -192,18 +197,21 @@ MyDsl().apply {
192197
// nicely typed.
193198
foo = "bar"
194199

200+
// but we never defined a bar property
195201
this["bar"] = "foo"
196-
this["going_off_script"] = listOf(
202+
// or this ...
203+
this["whatever"] = listOf(
197204
MyDsl().apply {
198-
this["anything"] = "is possible"
205+
this["you"] = "can add anything you want"
199206
},
200207
42
201208
)
209+
210+
// RawJson is a Kotlin value class
202211
this["inline_json"] = RawJson("""
203212
{
204213
"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",
207215
}
208216
""".trimIndent())
209217
}
@@ -213,40 +221,41 @@ MyDsl().apply {
213221
{
214222
"foo": "bar",
215223
"bar": "foo",
216-
"going_off_script": [
224+
"whatever": [
217225
{
218-
"anything": "is possible"
226+
"you": "can add anything you want"
219227
},
220228
42
221229
],
222230
"inline_json": {
223231
"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",
226233
}
227234
}
228235
```
229236

230237
### snake_casing, custom names, defaults
231238

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
233240
camel case for its identifiers and it has certain things that you can't redefine.
234-
235241
Like the `size` property on `Map`, which is implemented by JsonDsl; or certain keywords.
236242

237243
```kotlin
238244
class MyDsl : JsonDsl(
245+
// this will snake case all the names
239246
namingConvention = PropertyNamingConvention.ConvertToSnakeCase
240247
) {
241-
// this will be snake cased
248+
// -> camel_case
242249
var camelCase by property<Boolean>()
250+
// unfortunately Map defines a size val already
243251
var mySize by property<Int>(
244252
customPropertyName = "size"
245253
)
254+
// val is a keyword in Kotlin
246255
var myVal by property<String>(
247256
customPropertyName = "val"
248257
)
249-
// explicitly set name and provide a default
258+
// Kotlin has default values, JSON does not.
250259
var m by property(
251260
customPropertyName = "meaning_of_life",
252261
defaultValue = 42
@@ -271,11 +280,11 @@ MyDsl().apply {
271280

272281
### Custom values
273282

274-
Sometimes you might want to have the serialized version of a value be different
283+
Sometimes you want to have the serialized version of a value be different
275284
from the kotlin type that you are using. For this we have added the
276285
CustomValue interface.
277286

278-
A simple use case for this could be Enums:
287+
A simple use case for this could be Enums:
279288

280289
```kotlin
281290
enum class Grades(override val value: Double) : CustomValue<Double> {
@@ -560,15 +569,15 @@ reference for how to use JsonDsl.
560569

561570
## Multi platform
562571

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).
564573

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.
566575

567576
## Development and stability
568577

569578
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.
570579

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.
572581

573582
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.
574583

src/jvmTest/kotlin/com/jillesvangurp/jsondsl/readme/ReadmeGenerationTest.kt

+19-17
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,11 @@ val readmeMd = sourceGitRepository.md {
8585
subSection("Common Kotlin Types") {
8686
+"""
8787
JSON is a fairly simple data format. It has numbers, booleans, strings, lists and dictionaries. And null
88-
values.
89-
90-
Kotlin has a bit richer type system and mapping that to JSON is key to providing rich Kotlin DSL.
88+
values. Kotlin has a bit richer type system and mapping that to JSON is key to providing rich Kotlin DSL.
9189
9290
JsonDsl does a best effort to do map Kotlin types correctly to the intended JSON equivalent.
9391
94-
It understands all the primitives, Maps and Lists. But also Arrays, Sets, Sequences.
92+
It understands all the primitives, Maps and Lists. But also Arrays, Sets, Sequences, etc.
9593
And of course other JsonDsl classes, so you can nest them. And it falls back to using
9694
`toString()` for everything else.
9795
""".trimIndent()
@@ -122,14 +120,14 @@ val readmeMd = sourceGitRepository.md {
122120
idontknow = mapOf(
123121
"arrays" to arrayOf(
124122
1, 2, "3", 4.0,
125-
mapOf("this" to "is valid JSON")
123+
mapOf("this" to "is valid JSON"), "mixing types is allowed in JSON"
126124
),
127125
"sequences" to sequenceOf(1,"2",3.0)
128126
)
129127
}
130128
}.result.getOrThrow()!!.let {
131129
+"""
132-
This does the right things with all the Kotlin types, including `Any`:
130+
This does the right things with all the used Kotlin types, including `Any`:
133131
""".trimIndent()
134132
mdCodeBlock(it.json(true), type = "json")
135133
}
@@ -149,18 +147,21 @@ val readmeMd = sourceGitRepository.md {
149147
// nicely typed.
150148
foo = "bar"
151149

150+
// but we never defined a bar property
152151
this["bar"] = "foo"
153-
this["going_off_script"] = listOf(
152+
// or this ...
153+
this["whatever"] = listOf(
154154
MyDsl().apply {
155-
this["anything"] = "is possible"
155+
this["you"] = "can add anything you want"
156156
},
157157
42
158158
)
159+
160+
// RawJson is a Kotlin value class
159161
this["inline_json"] = RawJson("""
160162
{
161163
"if":"you need to",
162-
"you":"can even add json in string form",
163-
"RawJson":"is a value class"
164+
"you":"can even add json in string form",
164165
}
165166
""".trimIndent())
166167
}
@@ -170,26 +171,28 @@ val readmeMd = sourceGitRepository.md {
170171
}
171172
subSection("snake_casing, custom names, defaults") {
172173
+"""
173-
A lot of JSON dialects use snake cased dictionary keys. Kotlin of course uses
174+
A lot of JSON dialects use snake cased field names. Kotlin of course uses
174175
camel case for its identifiers and it has certain things that you can't redefine.
175-
176176
Like the `size` property on `Map`, which is implemented by JsonDsl; or certain keywords.
177177
178178
""".trimIndent()
179179

180180
example {
181181
class MyDsl : JsonDsl(
182+
// this will snake case all the names
182183
namingConvention = PropertyNamingConvention.ConvertToSnakeCase
183184
) {
184-
// this will be snake cased
185+
// -> camel_case
185186
var camelCase by property<Boolean>()
187+
// unfortunately Map defines a size val already
186188
var mySize by property<Int>(
187189
customPropertyName = "size"
188190
)
191+
// val is a keyword in Kotlin
189192
var myVal by property<String>(
190193
customPropertyName = "val"
191194
)
192-
// explicitly set name and provide a default
195+
// Kotlin has default values, JSON does not.
193196
var m by property(
194197
customPropertyName = "meaning_of_life",
195198
defaultValue = 42
@@ -207,12 +210,11 @@ val readmeMd = sourceGitRepository.md {
207210
}
208211
subSection("Custom values") {
209212
+"""
210-
Sometimes you might want to have the serialized version of a value be different
213+
Sometimes you want to have the serialized version of a value be different
211214
from the kotlin type that you are using. For this we have added the
212215
CustomValue interface.
213216
214-
A simple use case for this could be Enums:
215-
217+
A simple use case for this could be Enums:
216218
""".trimIndent()
217219

218220
exampleFromSnippet(ReadmeGenerationTest::class,"grades-enum")

0 commit comments

Comments
 (0)