Skip to content

Commit c75dc27

Browse files
committed
Merge branch 'release/0.3'
2 parents b1b74d5 + b29c2d8 commit c75dc27

File tree

4 files changed

+124
-74
lines changed

4 files changed

+124
-74
lines changed

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Adds [JSON Pointer (RFC 6901)](https://tools.ietf.org/html/rfc6901) support to
66
and modifying original fork by [John Lombardo](https://github.com/johnnylambada/gson).
77

88
## Usage
9-
This library provides a simple lookup implementation, that tries to always provide a valid Kotlin object for quick and easy dereferencing and direct usage of JSON pointers to read values from Gson's JsonParser when you don't want to keep making specific Kotlin data classes for one off use cases or specific error handling.
9+
This library provides a simple lookup implementation, that tries to always provide a valid Kotlin object for quick and easy dereferencing and direct usage of JSON pointers to read values from Gson's JsonParser when you don't want to keep making specific Kotlin data classes for one off use cases or specific error handling. By using the provided _safeX_ extension properties on JsonElement, you can rest assured that you'll always get a good value back instead of an Exception at runtime.
1010

1111
### Parse JSON using Gson
1212
Consider a JSON error response that you're receiving from your API like this:
@@ -36,16 +36,22 @@ Once you have a JsonElement you can get a JsonPointer with it as root, using the
3636
//pointer is JsonPointer
3737
```
3838

39-
Once you have a JsonPointer, you can directly access values anywhere in the whole JSON tree using pointer syntax:
39+
Once you have a JsonPointer, you can directly access a JsonPointer anywhere in the whole JSON tree using pointer syntax:
4040
```kotlin
41-
val message = pointer.at("/email/0")
41+
val message = pointer.at("/email/0").safeString
4242
//message is the first error String, "Enter a valid email address"
4343
```
4444

45-
**Note:** The `at()` function always tries to return a valid value. It will never return a null. In case of a JSON null, it will return an empty string, `""`
45+
###Safe value retreival
46+
The `at()` function always returns a valid JsonElement, even when there is no match. You can use the standard `isJsonPrimitive` or other similar methods to determine what you got back. You can more easily use the provided convenience get properties to get either the actual value at the node or a default value that you can check and flow against.
47+
48+
* `JsonElement.safeNumber` returns any `Number` found at the pointer, or a `0`
49+
* `JsonElement.safeString` returns any `String` found at the pointer, or a `""`
50+
* `JsonElement.safeBoolean` returns any `Boolean` found at the pointer, or a `false`
51+
* `JsonElement.safeJsonObject` returns any `JsonObject` found at the pointer, or a `JsonObject()`, which works out to an empty JSON object: `{}`
52+
* `JsonElement.safeJsonArray` returns any `JsonArray` found at the pointer, or a `JsonArray()`, which works out to an empty JSON Array, `[]`
53+
4654

47-
### Access a JsonElement
48-
**(Optional)** In case you _do_ need to get access to a JsonElement and not the actual value within it, you can use the `dereference()` method which will return a normal `JsonElement` for further evaluation.
4955

5056
## Installation
5157
GsonPointer uses JitPack.io to distribute the library.

com.traversient.gsonpointer/src/main/java/com/traversient/gsonpointer/JsonPointer.kt

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,24 @@ import com.google.gson.JsonObject
2121
* {"foo":{"bar":"baz"}}
2222
` *
2323
*/
24-
class JsonPointer(val element: JsonElement) {
25-
26-
fun at(pointer: String): Any{
27-
val elem = dereference(pointer)
28-
return when{
29-
elem == null -> ""
30-
elem.isJsonObject -> elem.asJsonObject
31-
elem.isJsonArray -> elem.asJsonArray
32-
elem.isJsonPrimitive && elem.asJsonPrimitive.isBoolean -> elem.asBoolean
33-
elem.isJsonPrimitive && elem.asJsonPrimitive.isString -> elem.asString
34-
elem.isJsonPrimitive && elem.asJsonPrimitive.isNumber -> elem.asNumber
35-
else -> ""
36-
}
37-
}
24+
class JsonPointer internal constructor(val element: JsonElement) {
3825

39-
fun dereference(pointer: String, generation: Int = 0): JsonElement? {
26+
@JvmOverloads
27+
fun at(pointer: String, generation: Int = 0): JsonElement {
4028
val list = getPath(pointer, false)
4129
return list[list.size - 1 - generation]
4230
}
4331

4432
operator fun set(pointer: String, value: JsonElement) {
4533
val last = getLastElement(pointer)
46-
val `object` = last.element!!.asJsonObject
34+
val `object` = last.element.asJsonObject
4735
`object`.remove(last.name)
4836
`object`.add(last.name, value)
4937
}
5038

51-
private fun getPath(pointer: String, mutative: Boolean): List<JsonElement?> {
39+
private fun getPath(pointer: String, mutative: Boolean): List<JsonElement> {
5240
val tokens = pointer.split("/".toRegex()).toTypedArray()
53-
val ret = ArrayList<JsonElement?>(tokens.size)
41+
val ret = ArrayList<JsonElement>(tokens.size)
5442
var element: JsonElement? = null
5543
for (i in tokens.indices) {
5644
val token = unescape(tokens[i])
@@ -81,10 +69,10 @@ class JsonPointer(val element: JsonElement) {
8169
null
8270
}
8371
}
84-
ret.add(element)
8572
if (element == null) {
8673
break
8774
}
75+
ret.add(element)
8876
}
8977
}
9078
return ret
@@ -144,7 +132,7 @@ class JsonPointer(val element: JsonElement) {
144132
}
145133
}
146134

147-
private class Element(internal val element: JsonElement?, internal val name: String)
135+
private class Element(internal val element: JsonElement, internal val name: String)
148136
}
149137

150138
//Extension property on JsonElement to get a pointer
@@ -153,3 +141,42 @@ val JsonElement.pointer: JsonPointer
153141
return JsonPointer(this)
154142
}
155143

144+
val JsonElement.safeNumber: Number
145+
get() {
146+
if (this.isJsonPrimitive && !this.isJsonNull && this.asJsonPrimitive.isNumber) {
147+
return this.asNumber
148+
}
149+
return 0
150+
}
151+
152+
val JsonElement.safeString: String
153+
get() {
154+
if (this.isJsonPrimitive && !this.isJsonNull && this.asJsonPrimitive.isString) {
155+
return this.asString
156+
}
157+
return ""
158+
}
159+
160+
val JsonElement.safeBoolean: Boolean
161+
get() {
162+
if (this.isJsonPrimitive && !this.isJsonNull && this.asJsonPrimitive.isBoolean) {
163+
return this.asBoolean
164+
}
165+
return false
166+
}
167+
168+
val JsonElement.safeJsonObject: JsonObject
169+
get() {
170+
if (!this.isJsonNull && this.isJsonObject) {
171+
return this.asJsonObject
172+
}
173+
return JsonObject()
174+
}
175+
176+
val JsonElement.safeJsonArray: JsonArray
177+
get() {
178+
if (!this.isJsonNull && this.isJsonArray) {
179+
return this.asJsonArray
180+
}
181+
return JsonArray()
182+
}

com.traversient.gsonpointer/src/test/java/com/traversient/gsonpointer/JsonPointerTest.kt

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package com.traversient.gsonpointer
22

3+
import com.google.gson.*
34
import org.junit.Test
45

56
import org.junit.Assert.*
6-
import com.google.gson.JsonElement
7-
import com.google.gson.JsonObject
8-
import com.google.gson.JsonParser
9-
import com.google.gson.JsonPrimitive
107

118
import com.traversient.gsonpointer.JsonPointer
129

@@ -82,6 +79,35 @@ class JsonPointerTest {
8279
" \"m~n\": 8\n" +
8380
" }"
8481

82+
private val JSON_Multi = "{\n" +
83+
" \"widget\": {\n" +
84+
" \"debug\": \"on\",\n" +
85+
" \"window\": {\n" +
86+
" \"title\": \"Sample Konfabulator Widget\",\n" +
87+
" \"name\": \"main_window\",\n" +
88+
" \"width\": 500,\n" +
89+
" \"height\": 500\n" +
90+
" },\n" +
91+
" \"image\": {\n" +
92+
" \"src\": \"Images/Sun.png\",\n" +
93+
" \"name\": \"sun1\",\n" +
94+
" \"hOffset\": 250,\n" +
95+
" \"vOffset\": 250,\n" +
96+
" \"alignment\": \"center\"\n" +
97+
" },\n" +
98+
" \"text\": {\n" +
99+
" \"data\": \"Click Here\",\n" +
100+
" \"size\": 36,\n" +
101+
" \"style\": \"bold\",\n" +
102+
" \"name\": \"text1\",\n" +
103+
" \"hOffset\": 250,\n" +
104+
" \"vOffset\": 100,\n" +
105+
" \"alignment\": \"center\",\n" +
106+
" \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n" +
107+
" }\n" +
108+
" }\n" +
109+
"}"
110+
85111
@Test
86112
@Throws(Exception::class)
87113
/**
@@ -90,18 +116,18 @@ class JsonPointerTest {
90116
fun rfc6901Test() {
91117
val root = JsonParser().parse(JSON_6901)
92118
val json = root.pointer
93-
assertEquals(root, json.dereference(""))
94-
assertEquals(JsonParser().parse("[\"bar\", \"baz\"]"), json.dereference("/foo"))
95-
assertEquals("bar", json.dereference("/foo/0")!!.getAsString())
96-
assertEquals(0, json.dereference("/")!!.getAsInt())
97-
assertEquals(1, json.dereference("/a~1b")!!.getAsInt())
98-
assertEquals(2, json.dereference("/c%d")!!.getAsInt())
99-
assertEquals(3, json.dereference("/e^f")!!.getAsInt())
100-
assertEquals(4, json.dereference("/g|h")!!.getAsInt())
101-
assertEquals(5, json.dereference("/i\\j")!!.getAsInt())
102-
assertEquals(6, json.dereference("/k\"l")!!.getAsInt())
103-
assertEquals(7, json.dereference("/ ")!!.getAsInt())
104-
assertEquals(8, json.dereference("/m~0n")!!.getAsInt())
119+
assertEquals(root, json.at(""))
120+
assertEquals(JsonParser().parse("[\"bar\", \"baz\"]"), json.at("/foo"))
121+
assertEquals("bar", json.at("/foo/0")!!.getAsString())
122+
assertEquals(0, json.at("/")!!.getAsInt())
123+
assertEquals(1, json.at("/a~1b")!!.getAsInt())
124+
assertEquals(2, json.at("/c%d")!!.getAsInt())
125+
assertEquals(3, json.at("/e^f")!!.getAsInt())
126+
assertEquals(4, json.at("/g|h")!!.getAsInt())
127+
assertEquals(5, json.at("/i\\j")!!.getAsInt())
128+
assertEquals(6, json.at("/k\"l")!!.getAsInt())
129+
assertEquals(7, json.at("/ ")!!.getAsInt())
130+
assertEquals(8, json.at("/m~0n")!!.getAsInt())
105131
}
106132

107133

@@ -118,7 +144,7 @@ class JsonPointerTest {
118144
fun getRootTest() {
119145
val expected = JsonParser().parse(JSON)
120146
val json = expected.pointer
121-
val actual = json.dereference("")
147+
val actual = json.at("")
122148
assertEquals(expected, actual)
123149
}
124150

@@ -127,7 +153,7 @@ class JsonPointerTest {
127153
fun getSimpleStringTest() {
128154
val json = JsonParser().parse(JSON).pointer
129155
val expected = "library of congress"
130-
val actual = json.dereference("/library/name")!!.getAsString()
156+
val actual = json.at("/library/name")!!.getAsString()
131157
assertEquals(expected, actual)
132158
}
133159

@@ -136,7 +162,7 @@ class JsonPointerTest {
136162
fun getSimpleStringInArrayTest() {
137163
val json = JsonParser().parse(JSON).pointer
138164
val expected = "sci-fi"
139-
val actual = json.dereference("/library/section/0/name")!!.getAsString()
165+
val actual = json.at("/library/section/0/name")!!.getAsString()
140166
assertEquals(expected, actual)
141167
}
142168

@@ -145,7 +171,7 @@ class JsonPointerTest {
145171
fun getDeepArrayTest() {
146172
val json = JsonParser().parse(JSON).pointer
147173
val expected = "Jerry Pournelle"
148-
val actual = json.dereference("/library/section/0/title/0/book/author/1")!!.getAsString()
174+
val actual = json.at("/library/section/0/title/0/book/author/1")!!.getAsString()
149175
assertEquals(expected, actual)
150176
}
151177

@@ -156,19 +182,10 @@ class JsonPointerTest {
156182
val expected = "hello world"
157183
val pointer = "/this/is/a/new/thing"
158184
json.set(pointer, JsonPrimitive(expected))
159-
val actual = json.dereference(pointer)!!.getAsString()
185+
val actual = json.at(pointer)!!.getAsString()
160186
assertEquals(expected, actual)
161187
}
162188

163-
@Test
164-
fun alwaysValidValue() {
165-
val json = JsonParser().parse(JSON).pointer
166-
assertTrue(json.at("/library") is JsonObject)
167-
assertEquals(json.at("/"), "")
168-
assertEquals(json.at("/nothing"), "")
169-
assertEquals(json.at("/"), "")
170-
}
171-
172189
@Test
173190
@Throws(Exception::class)
174191
fun setStringTestWithArrays() {
@@ -178,11 +195,11 @@ class JsonPointerTest {
178195
val thingPointer = "/4/this/is/a/0/new/thing"
179196
val pointer = JsonParser().parse(originalJson).pointer
180197

181-
assertEquals(null, pointer.dereference(thingPointer))
198+
assertEquals(JsonArray(), pointer.at(thingPointer))
182199
pointer.set(thingPointer, JsonPrimitive(originalValue))
183-
assertEquals(originalValue, pointer.dereference(thingPointer)!!.getAsString())
200+
assertEquals(originalValue, pointer.at(thingPointer)!!.getAsString())
184201
pointer.set(thingPointer, JsonPrimitive(expectedValue))
185-
assertEquals(expectedValue, pointer.dereference(thingPointer)!!.getAsString())
202+
assertEquals(expectedValue, pointer.at(thingPointer)!!.getAsString())
186203
}
187204

188205
@Test
@@ -194,11 +211,11 @@ class JsonPointerTest {
194211
val thingPointer = "/this/is/a/0/new/thing"
195212
val pointer = JsonParser().parse(originalJson).pointer
196213

197-
assertEquals(null, pointer.dereference(thingPointer))
214+
assertEquals(JsonObject(), pointer.at(thingPointer))
198215
pointer.set(thingPointer, JsonPrimitive(originalValue))
199-
assertEquals(originalValue, pointer.dereference(thingPointer)!!.getAsString())
216+
assertEquals(originalValue, pointer.at(thingPointer)!!.getAsString())
200217
pointer.set(thingPointer, JsonPrimitive(expectedValue))
201-
assertEquals(expectedValue, pointer.dereference(thingPointer)!!.getAsString())
218+
assertEquals(expectedValue, pointer.at(thingPointer)!!.getAsString())
202219
}
203220

204221
@Test
@@ -210,11 +227,11 @@ class JsonPointerTest {
210227
val thingPointer = "/this/is/a/0/new/thing"
211228
val pointer = JsonParser().parse(originalJson).pointer
212229

213-
assertEquals(null, pointer.dereference(thingPointer))
230+
assertEquals(JsonObject(), pointer.at(thingPointer))
214231
pointer.set(thingPointer, JsonPrimitive(originalValue))
215-
assertEquals(originalValue, pointer.dereference(thingPointer)!!.getAsBoolean())
232+
assertEquals(originalValue, pointer.at(thingPointer)!!.getAsBoolean())
216233
pointer.set(thingPointer, JsonPrimitive(expectedValue))
217-
assertEquals(expectedValue, pointer.dereference(thingPointer)!!.getAsBoolean())
234+
assertEquals(expectedValue, pointer.at(thingPointer)!!.getAsBoolean())
218235
}
219236

220237
@Test
@@ -225,9 +242,9 @@ class JsonPointerTest {
225242
val reasonPointer = "/data/extensions/currentVisit/reason"
226243
val pointer = JsonParser().parse(originalJson).pointer
227244

228-
assertEquals(null, pointer.dereference(reasonPointer))
245+
assertEquals(JsonObject(), pointer.at(reasonPointer))
229246
pointer.set(reasonPointer, JsonPrimitive(expectedValue))
230-
assertEquals(expectedValue, pointer.dereference(reasonPointer)!!.getAsString())
247+
assertEquals(expectedValue, pointer.at(reasonPointer)!!.getAsString())
231248
}
232249

233250
@Test
@@ -240,13 +257,13 @@ class JsonPointerTest {
240257
val value2 = 1
241258
val value3 = true
242259

243-
assertEquals(null, pointer.dereference(thingPointer))
260+
assertEquals(JsonObject(), pointer.at(thingPointer))
244261
pointer.set(thingPointer, JsonPrimitive(value1))
245-
assertEquals(value1, pointer.dereference(thingPointer)!!.getAsString())
262+
assertEquals(value1, pointer.at(thingPointer)!!.getAsString())
246263
pointer.set(thingPointer, JsonPrimitive(value2))
247-
assertEquals(value2, pointer.dereference(thingPointer)!!.getAsInt())
264+
assertEquals(value2, pointer.at(thingPointer)!!.getAsInt())
248265
pointer.set(thingPointer, JsonPrimitive(value3))
249-
assertEquals(value3, pointer.dereference(thingPointer)!!.getAsBoolean())
266+
assertEquals(value3, pointer.at(thingPointer)!!.getAsBoolean())
250267
}
251268

252269
@Test
@@ -257,9 +274,9 @@ class JsonPointerTest {
257274
val reasonPointer = "/foo/bar"
258275
val pointer = JsonParser().parse(originalJson).pointer
259276

260-
assertEquals(null, pointer.dereference(reasonPointer))
277+
assertEquals(JsonObject(), pointer.at(reasonPointer))
261278
pointer.set(reasonPointer, JsonPrimitive(expectedValue))
262-
assertEquals(expectedValue, pointer.dereference(reasonPointer)!!.getAsString())
279+
assertEquals(expectedValue, pointer.at(reasonPointer)!!.getAsString())
263280
println(pointer.element.toString())
264281
}
265282
}

0 commit comments

Comments
 (0)