1
+ @file:JvmName(" ObjectWrappers" )
2
+
3
+ package io.github.optimumcode.json.schema.objects.wrapper
4
+
5
+ import io.github.optimumcode.json.schema.ExperimentalApi
6
+ import io.github.optimumcode.json.schema.model.AbstractElement
7
+ import io.github.optimumcode.json.schema.model.ArrayElement
8
+ import io.github.optimumcode.json.schema.model.ObjectElement
9
+ import io.github.optimumcode.json.schema.model.PrimitiveElement
10
+ import kotlin.jvm.JvmInline
11
+ import kotlin.jvm.JvmName
12
+ import kotlin.jvm.JvmOverloads
13
+
14
+ @ExperimentalApi
15
+ public class WrappingConfiguration internal constructor(
16
+ public val allowSets : Boolean = false ,
17
+ )
18
+
19
+ @ExperimentalApi
20
+ @JvmOverloads
21
+ public fun wrappingConfiguration (allowSets : Boolean = false): WrappingConfiguration = WrappingConfiguration (allowSets)
22
+
23
+ /* *
24
+ * Returns an [AbstractElement] produced by converting the [obj] value.
25
+ * The [configuration] allows conversion customization.
26
+ *
27
+ * # The supported types
28
+ *
29
+ * ## Simple values:
30
+ * * [String]
31
+ * * [Byte]
32
+ * * [Short]
33
+ * * [Int]
34
+ * * [Long]
35
+ * * [Float]
36
+ * * [Double]
37
+ * * [Boolean]
38
+ * * `null`
39
+ *
40
+ * ## Structures:
41
+ * * [Map] -> keys MUST have a [String] type, values MUST be one of the supported types
42
+ * * [List] -> elements MUST be one of the supported types
43
+ * * [Array] -> elements MUST be one of the supported types
44
+ *
45
+ * If [WrappingConfiguration.allowSets] is enabled [Set] is also converted to [ArrayElement].
46
+ * Please be aware that in order to have consistent verification results
47
+ * the [Set] must be one of the ORDERED types, e.g. [LinkedHashSet].
48
+ */
49
+ @ExperimentalApi
50
+ public fun wrapAsElement (
51
+ obj : Any? ,
52
+ configuration : WrappingConfiguration = WrappingConfiguration (),
53
+ ): AbstractElement {
54
+ if (obj == null ) {
55
+ return NullWrapper
56
+ }
57
+ return when {
58
+ obj is Map <* , * > -> checkKeysAndWrap(obj, configuration)
59
+ obj is List <* > -> ListWrapper (obj.map { wrapAsElement(it, configuration) })
60
+ obj is Array <* > -> ListWrapper (obj.map { wrapAsElement(it, configuration) })
61
+ obj is Set <* > && configuration.allowSets ->
62
+ ListWrapper (obj.map { wrapAsElement(it, configuration) })
63
+ obj is String || obj is Number || obj is Boolean -> PrimitiveWrapper (numberToSupportedTypeOrOriginal(obj))
64
+ else -> error(" unsupported type to wrap: ${obj::class } " )
65
+ }
66
+ }
67
+
68
+ private fun numberToSupportedTypeOrOriginal (obj : Any ): Any =
69
+ when (obj) {
70
+ !is Number -> obj
71
+ is Double , is Long -> obj
72
+ is Byte , is Short , is Int -> obj.toLong()
73
+ is Float -> obj.toDoubleSafe()
74
+ else -> error(" unsupported number type: ${obj::class } " )
75
+ }
76
+
77
+ private fun Float.toDoubleSafe (): Double {
78
+ val double = toDouble()
79
+ // in some cases the conversion from float to double
80
+ // can introduce a difference between numbers. (e.g. 42.2f -> 42.2)
81
+ // In this case, the only way (at the moment) is to try parsing
82
+ // the double from float converted to string
83
+ val floatAsString = toString()
84
+ if (double.toString() == floatAsString) {
85
+ return double
86
+ }
87
+ return floatAsString.toDouble()
88
+ }
89
+
90
+ private fun checkKeysAndWrap (
91
+ map : Map <* , * >,
92
+ configuration : WrappingConfiguration ,
93
+ ): ObjectWrapper {
94
+ if (map.isEmpty()) {
95
+ return ObjectWrapper (emptyMap())
96
+ }
97
+
98
+ require(map.keys.all { it is String }) {
99
+ val notStrings =
100
+ map.keys.asSequence().filterNot { it is String }.mapTo(hashSetOf()) { key ->
101
+ key?.let { it::class .simpleName } ? : " null"
102
+ }.joinToString()
103
+ " map keys must be strings, found: $notStrings "
104
+ }
105
+
106
+ @Suppress(" UNCHECKED_CAST" )
107
+ val elementsMap =
108
+ map.mapValues { (_, value) ->
109
+ wrapAsElement(value, configuration)
110
+ } as Map <String , AbstractElement >
111
+ return ObjectWrapper (elementsMap)
112
+ }
113
+
114
+ @JvmInline
115
+ private value class ObjectWrapper (
116
+ private val map : Map <String , AbstractElement >,
117
+ ) : ObjectElement {
118
+ override val keys: Set <String >
119
+ get() = map.keys
120
+
121
+ override fun get (key : String ): AbstractElement ? = map[key]
122
+
123
+ override fun contains (key : String ): Boolean = map.containsKey(key)
124
+
125
+ override val size: Int
126
+ get() = map.size
127
+
128
+ override fun iterator (): Iterator <Pair <String , AbstractElement >> =
129
+ map.asSequence().map { (key, value) -> key to value }.iterator()
130
+
131
+ override fun toString (): String = map.toString()
132
+ }
133
+
134
+ @JvmInline
135
+ private value class ListWrapper (
136
+ private val list : List <AbstractElement >,
137
+ ) : ArrayElement {
138
+ override fun iterator (): Iterator <AbstractElement > = list.iterator()
139
+
140
+ override fun get (index : Int ): AbstractElement = list[index]
141
+
142
+ override val size: Int
143
+ get() = list.size
144
+
145
+ override fun toString (): String = list.toString()
146
+ }
147
+
148
+ @JvmInline
149
+ private value class PrimitiveWrapper (
150
+ private val value : Any ,
151
+ ) : PrimitiveElement {
152
+ override val isNull: Boolean
153
+ get() = false
154
+ override val isString: Boolean
155
+ get() = value is String
156
+ override val isBoolean: Boolean
157
+ get() = value is Boolean
158
+ override val isNumber: Boolean
159
+ get() = value is Number
160
+ override val longOrNull: Long?
161
+ get() = value as ? Long
162
+ override val doubleOrNull: Double?
163
+ get() = value as ? Double
164
+ override val content: String
165
+ get() = value.toString()
166
+
167
+ override fun toString (): String = value.toString()
168
+ }
169
+
170
+ private data object NullWrapper : PrimitiveElement {
171
+ override val isNull: Boolean
172
+ get() = true
173
+ override val isString: Boolean
174
+ get() = false
175
+ override val isBoolean: Boolean
176
+ get() = false
177
+ override val isNumber: Boolean
178
+ get() = false
179
+ override val longOrNull: Long?
180
+ get() = null
181
+ override val doubleOrNull: Double?
182
+ get() = null
183
+ override val content: String
184
+ get() = " null"
185
+
186
+ override fun toString (): String = " null"
187
+ }
0 commit comments