1 /*
2  * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 @file:OptIn(ExperimentalContracts::class)
5 
6 package kotlinx.serialization.json
7 
8 import kotlinx.serialization.ExperimentalSerializationApi
9 import kotlin.contracts.*
10 import kotlin.jvm.JvmName
11 
12 /**
13  * Builds [JsonObject] with the given [builderAction] builder.
14  * Example of usage:
15  * ```
16  * val json = buildJsonObject {
17  *     put("booleanKey", true)
18  *     putJsonArray("arrayKey") {
19  *         for (i in 1..10) add(i)
20  *     }
21  *     putJsonObject("objectKey") {
22  *         put("stringKey", "stringValue")
23  *     }
24  * }
25  * ```
26  */
buildJsonObjectnull27 public inline fun buildJsonObject(builderAction: JsonObjectBuilder.() -> Unit): JsonObject {
28     contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
29     val builder = JsonObjectBuilder()
30     builder.builderAction()
31     return builder.build()
32 }
33 
34 
35 /**
36  * Builds [JsonArray] with the given [builderAction] builder.
37  * Example of usage:
38  * ```
39  * val json = buildJsonArray {
40  *     add(true)
41  *     addJsonArray {
42  *         for (i in 1..10) add(i)
43  *     }
44  *     addJsonObject {
45  *         put("stringKey", "stringValue")
46  *     }
47  * }
48  * ```
49  */
buildJsonArraynull50 public inline fun buildJsonArray(builderAction: JsonArrayBuilder.() -> Unit): JsonArray {
51     contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
52     val builder = JsonArrayBuilder()
53     builder.builderAction()
54     return builder.build()
55 }
56 
57 /**
58  * DSL builder for a [JsonObject]. To create an instance of builder, use [buildJsonObject] build function.
59  */
60 @JsonDslMarker
61 public class JsonObjectBuilder @PublishedApi internal constructor() {
62 
63     private val content: MutableMap<String, JsonElement> = linkedMapOf()
64 
65     /**
66      * Add the given JSON [element] to a resulting JSON object using the given [key].
67      *
68      * Returns the previous value associated with [key], or `null` if the key was not present.
69      */
putnull70     public fun put(key: String, element: JsonElement): JsonElement? = content.put(key, element)
71 
72     @PublishedApi
73     internal fun build(): JsonObject = JsonObject(content)
74 }
75 
76 /**
77  * Add the [JSON object][JsonObject] produced by the [builderAction] function to a resulting JSON object using the given [key].
78  *
79  * Returns the previous value associated with [key], or `null` if the key was not present.
80  */
81 public fun JsonObjectBuilder.putJsonObject(key: String, builderAction: JsonObjectBuilder.() -> Unit): JsonElement? =
82     put(key, buildJsonObject(builderAction))
83 
84 /**
85  * Add the [JSON array][JsonArray] produced by the [builderAction] function to a resulting JSON object using the given [key].
86  *
87  * Returns the previous value associated with [key], or `null` if the key was not present.
88  */
89 public fun JsonObjectBuilder.putJsonArray(key: String, builderAction: JsonArrayBuilder.() -> Unit): JsonElement? =
90     put(key, buildJsonArray(builderAction))
91 
92 /**
93  * Add the given boolean [value] to a resulting JSON object using the given [key].
94  *
95  * Returns the previous value associated with [key], or `null` if the key was not present.
96  */
97 public fun JsonObjectBuilder.put(key: String, value: Boolean?): JsonElement? = put(key, JsonPrimitive(value))
98 
99 /**
100  * Add the given numeric [value] to a resulting JSON object using the given [key].
101  *
102  * Returns the previous value associated with [key], or `null` if the key was not present.
103  */
104 public fun JsonObjectBuilder.put(key: String, value: Number?): JsonElement? = put(key, JsonPrimitive(value))
105 
106 /**
107  * Add the given string [value] to a resulting JSON object using the given [key].
108  *
109  * Returns the previous value associated with [key], or `null` if the key was not present.
110  */
111 public fun JsonObjectBuilder.put(key: String, value: String?): JsonElement? = put(key, JsonPrimitive(value))
112 
113 /**
114  * Add `null` to a resulting JSON object using the given [key].
115  *
116  * Returns the previous value associated with [key], or `null` if the key was not present.
117  */
118 @ExperimentalSerializationApi
119 @Suppress("UNUSED_PARAMETER") // allows to call `put("key", null)`
120 public fun JsonObjectBuilder.put(key: String, value: Nothing?): JsonElement? = put(key, JsonNull)
121 
122 /**
123  * DSL builder for a [JsonArray]. To create an instance of builder, use [buildJsonArray] build function.
124  */
125 @JsonDslMarker
126 public class JsonArrayBuilder @PublishedApi internal constructor() {
127 
128     private val content: MutableList<JsonElement> = mutableListOf()
129 
130     /**
131      * Adds the given JSON [element] to a resulting JSON array.
132      *
133      * Always returns `true` similarly to [ArrayList] specification.
134      */
135     public fun add(element: JsonElement): Boolean {
136         content += element
137         return true
138     }
139 
140     /**
141      * Adds the given JSON [elements] to a resulting JSON array.
142      *
143      * @return `true` if the list was changed as the result of the operation.
144      */
145     @ExperimentalSerializationApi
146     public fun addAll(elements: Collection<JsonElement>): Boolean =
147         content.addAll(elements)
148 
149     @PublishedApi
150     internal fun build(): JsonArray = JsonArray(content)
151 }
152 
153 /**
154  * Adds the given boolean [value] to a resulting JSON array.
155  *
156  * Always returns `true` similarly to [ArrayList] specification.
157  */
addnull158 public fun JsonArrayBuilder.add(value: Boolean?): Boolean = add(JsonPrimitive(value))
159 
160 /**
161  * Adds the given numeric [value] to a resulting JSON array.
162  *
163  * Always returns `true` similarly to [ArrayList] specification.
164  */
165 public fun JsonArrayBuilder.add(value: Number?): Boolean = add(JsonPrimitive(value))
166 
167 /**
168  * Adds the given string [value] to a resulting JSON array.
169  *
170  * Always returns `true` similarly to [ArrayList] specification.
171  */
172 public fun JsonArrayBuilder.add(value: String?): Boolean = add(JsonPrimitive(value))
173 
174 /**
175  * Adds `null` to a resulting JSON array.
176  *
177  * Always returns `true` similarly to [ArrayList] specification.
178  */
179 @ExperimentalSerializationApi
180 @Suppress("UNUSED_PARAMETER") // allows to call `add(null)`
181 public fun JsonArrayBuilder.add(value: Nothing?): Boolean = add(JsonNull)
182 
183 /**
184  * Adds the [JSON object][JsonObject] produced by the [builderAction] function to a resulting JSON array.
185  *
186  * Always returns `true` similarly to [ArrayList] specification.
187  */
188 public fun JsonArrayBuilder.addJsonObject(builderAction: JsonObjectBuilder.() -> Unit): Boolean =
189     add(buildJsonObject(builderAction))
190 
191 /**
192  * Adds the [JSON array][JsonArray] produced by the [builderAction] function to a resulting JSON array.
193  *
194  * Always returns `true` similarly to [ArrayList] specification.
195  */
196 public fun JsonArrayBuilder.addJsonArray(builderAction: JsonArrayBuilder.() -> Unit): Boolean =
197     add(buildJsonArray(builderAction))
198 
199 /**
200  * Adds the given string [values] to a resulting JSON array.
201  *
202  * @return `true` if the list was changed as the result of the operation.
203  */
204 @JvmName("addAllStrings")
205 @ExperimentalSerializationApi
206 public fun JsonArrayBuilder.addAll(values: Collection<String?>): Boolean =
207     addAll(values.map(::JsonPrimitive))
208 
209 /**
210  * Adds the given boolean [values] to a resulting JSON array.
211  *
212  * @return `true` if the list was changed as the result of the operation.
213  */
214 @JvmName("addAllBooleans")
215 @ExperimentalSerializationApi
216 public fun JsonArrayBuilder.addAll(values: Collection<Boolean?>): Boolean =
217     addAll(values.map(::JsonPrimitive))
218 
219 /**
220  * Adds the given numeric [values] to a resulting JSON array.
221  *
222  * @return `true` if the list was changed as the result of the operation.
223  */
224 @JvmName("addAllNumbers")
225 @ExperimentalSerializationApi
226 public fun JsonArrayBuilder.addAll(values: Collection<Number?>): Boolean =
227     addAll(values.map(::JsonPrimitive))
228 
229 @DslMarker
230 internal annotation class JsonDslMarker
231