Skip to content

Commit 732573b

Browse files
committed
Added math directly in the mapping
1 parent ca57921 commit 732573b

3 files changed

Lines changed: 339 additions & 0 deletions

File tree

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,41 @@ GSJson.get(json, "friends.[#.first]|@join: | ") // Returns: "Dale | Roger | J
147147
GSJson.get(json, "children|@join:-") // Returns: "Sara-Alex-Jack"
148148
```
149149

150+
### Mathematical Operations
151+
152+
```kotlin
153+
val numbers = "[10, 20, 30, 40, 50]"
154+
155+
// Basic math operations on arrays
156+
GSJson.get(numbers, ".|@multiply:2") // Returns: [20.0, 40.0, 60.0, 80.0, 100.0]
157+
GSJson.get(numbers, ".|@add:5") // Returns: [15.0, 25.0, 35.0, 45.0, 55.0]
158+
GSJson.get(numbers, ".|@subtract:10") // Returns: [0.0, 10.0, 20.0, 30.0, 40.0]
159+
GSJson.get(numbers, ".|@divide:2") // Returns: [5.0, 10.0, 15.0, 20.0, 25.0]
160+
161+
// Advanced math operations
162+
GSJson.get(numbers, ".|@abs") // Returns: absolute values
163+
GSJson.get(numbers, ".|@round:1") // Returns: rounded to 1 decimal place
164+
GSJson.get(numbers, ".|@power:2") // Returns: squared values
165+
166+
// Chain mathematical operations with aggregation
167+
GSJson.get(json, "friends.[#.age]|@add:5|@sum") // Add 5 to each age, then sum
168+
```
169+
170+
### Fallback Default Values
171+
172+
```kotlin
173+
// Provide defaults for missing or null values
174+
GSJson.get(json, "name.middle", "J") // Returns: "J" (field doesn't exist)
175+
GSJson.get(json, "age", 0) // Returns: 37 (field exists)
176+
GSJson.get(json, "missing.field", "default") // Returns: "default"
177+
GSJson.get(json, "friends.[99].name", "Unknown") // Returns: "Unknown" (index out of bounds)
178+
179+
// Works with all data types
180+
GSJson.get(json, "missing.number", 42) // Numeric default
181+
GSJson.get(json, "missing.boolean", true) // Boolean default
182+
GSJson.get(json, "missing.list", listOf("a", "b")) // List default
183+
```
184+
150185
### Path Chaining
151186

152187
```kotlin

src/main/kotlin/com/transportial/GSJson.kt

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.node.*
66
import org.json.JSONArray
77
import org.json.JSONObject
88
import java.time.Instant
9+
import kotlin.math.*
910

1011

1112
/**
@@ -44,6 +45,25 @@ object GSJson {
4445
}
4546
}
4647

48+
/**
49+
* Getting JSON value from string and selection path with fallback default
50+
*
51+
* @param json: as string
52+
* @param selection: The selection string
53+
* @param defaultValue: Default value to return if path doesn't exist or is null
54+
*
55+
* @return value according to the selection path or default value
56+
*/
57+
fun get(json: String, selection: String, defaultValue: Any): Any {
58+
inputType = DataType.STRING
59+
val result = if (isJsonLinesSelection(selection)) {
60+
handleJsonLines(json, selection)
61+
} else {
62+
parseAndGet(objectMapper.readTree(json), selection)
63+
}
64+
return result ?: defaultValue
65+
}
66+
4767
/**
4868
* Getting JSON result from string and selection path
4969
*
@@ -70,6 +90,21 @@ object GSJson {
7090
inputType = DataType.GSON
7191
return parseAndGet(objectMapper.readTree(json.toString()), selection)
7292
}
93+
94+
/**
95+
* Getting JSON value from JSONObject and selection path with fallback default
96+
*
97+
* @param json: as JSONObject
98+
* @param selection: The selection string
99+
* @param defaultValue: Default value to return if path doesn't exist or is null
100+
*
101+
* @return value according to the selection path or default value
102+
*/
103+
fun get(json: JSONObject, selection: String, defaultValue: Any): Any {
104+
inputType = DataType.GSON
105+
val result = parseAndGet(objectMapper.readTree(json.toString()), selection)
106+
return result ?: defaultValue
107+
}
73108

74109
/**
75110
* Getting JSON value from JSONArray and selection path
@@ -83,6 +118,21 @@ object GSJson {
83118
inputType = DataType.GSON
84119
return parseAndGet(objectMapper.readTree(json.toString()), selection)
85120
}
121+
122+
/**
123+
* Getting JSON value from JSONArray and selection path with fallback default
124+
*
125+
* @param json: as JSONArray
126+
* @param selection: The selection string
127+
* @param defaultValue: Default value to return if path doesn't exist or is null
128+
*
129+
* @return value according to the selection path or default value
130+
*/
131+
fun get(json: JSONArray, selection: String, defaultValue: Any): Any {
132+
inputType = DataType.GSON
133+
val result = parseAndGet(objectMapper.readTree(json.toString()), selection)
134+
return result ?: defaultValue
135+
}
86136

87137
/**
88138
* Getting JSON value from JsonNode and selection path
@@ -96,6 +146,21 @@ object GSJson {
96146
inputType = DataType.JACKSON
97147
return parseAndGet(json, selection)
98148
}
149+
150+
/**
151+
* Getting JSON value from JsonNode and selection path with fallback default
152+
*
153+
* @param json: JsonNode (Jackson JsonNode)
154+
* @param selection: The selection string
155+
* @param defaultValue: Default value to return if path doesn't exist or is null
156+
*
157+
* @return value according to the selection path or default value
158+
*/
159+
fun get(json: JsonNode, selection: String, defaultValue: Any): Any {
160+
inputType = DataType.JACKSON
161+
val result = parseAndGet(json, selection)
162+
return result ?: defaultValue
163+
}
99164

100165

101166
/**
@@ -1080,6 +1145,176 @@ object GSJson {
10801145
}
10811146
else -> json
10821147
}
1148+
"@multiply", "@mul" -> when (json) {
1149+
is ArrayNode -> {
1150+
val multiplier = argument?.toDoubleOrNull() ?: 1.0
1151+
val resultArray = objectMapper.createArrayNode()
1152+
json.forEach { node ->
1153+
val value = when {
1154+
node.isNumber -> node.asDouble() * multiplier
1155+
node.isTextual -> (node.asText().toDoubleOrNull() ?: 0.0) * multiplier
1156+
else -> 0.0
1157+
}
1158+
resultArray.add(value)
1159+
}
1160+
resultArray
1161+
}
1162+
else -> {
1163+
val multiplier = argument?.toDoubleOrNull() ?: 1.0
1164+
val value = when {
1165+
json.isNumber -> json.asDouble() * multiplier
1166+
json.isTextual -> (json.asText().toDoubleOrNull() ?: 0.0) * multiplier
1167+
else -> 0.0
1168+
}
1169+
objectMapper.valueToTree(value)
1170+
}
1171+
}
1172+
"@divide", "@div" -> when (json) {
1173+
is ArrayNode -> {
1174+
val divisor = argument?.toDoubleOrNull() ?: 1.0
1175+
if (divisor == 0.0) return json // Avoid division by zero
1176+
val resultArray = objectMapper.createArrayNode()
1177+
json.forEach { node ->
1178+
val value = when {
1179+
node.isNumber -> node.asDouble() / divisor
1180+
node.isTextual -> (node.asText().toDoubleOrNull() ?: 0.0) / divisor
1181+
else -> 0.0
1182+
}
1183+
resultArray.add(value)
1184+
}
1185+
resultArray
1186+
}
1187+
else -> {
1188+
val divisor = argument?.toDoubleOrNull() ?: 1.0
1189+
if (divisor == 0.0) return json // Avoid division by zero
1190+
val value = when {
1191+
json.isNumber -> json.asDouble() / divisor
1192+
json.isTextual -> (json.asText().toDoubleOrNull() ?: 0.0) / divisor
1193+
else -> 0.0
1194+
}
1195+
objectMapper.valueToTree(value)
1196+
}
1197+
}
1198+
"@add", "@plus" -> when (json) {
1199+
is ArrayNode -> {
1200+
val addend = argument?.toDoubleOrNull() ?: 0.0
1201+
val resultArray = objectMapper.createArrayNode()
1202+
json.forEach { node ->
1203+
val value = when {
1204+
node.isNumber -> node.asDouble() + addend
1205+
node.isTextual -> (node.asText().toDoubleOrNull() ?: 0.0) + addend
1206+
else -> addend
1207+
}
1208+
resultArray.add(value)
1209+
}
1210+
resultArray
1211+
}
1212+
else -> {
1213+
val addend = argument?.toDoubleOrNull() ?: 0.0
1214+
val value = when {
1215+
json.isNumber -> json.asDouble() + addend
1216+
json.isTextual -> (json.asText().toDoubleOrNull() ?: 0.0) + addend
1217+
else -> addend
1218+
}
1219+
objectMapper.valueToTree(value)
1220+
}
1221+
}
1222+
"@subtract", "@sub" -> when (json) {
1223+
is ArrayNode -> {
1224+
val subtrahend = argument?.toDoubleOrNull() ?: 0.0
1225+
val resultArray = objectMapper.createArrayNode()
1226+
json.forEach { node ->
1227+
val value = when {
1228+
node.isNumber -> node.asDouble() - subtrahend
1229+
node.isTextual -> (node.asText().toDoubleOrNull() ?: 0.0) - subtrahend
1230+
else -> -subtrahend
1231+
}
1232+
resultArray.add(value)
1233+
}
1234+
resultArray
1235+
}
1236+
else -> {
1237+
val subtrahend = argument?.toDoubleOrNull() ?: 0.0
1238+
val value = when {
1239+
json.isNumber -> json.asDouble() - subtrahend
1240+
json.isTextual -> (json.asText().toDoubleOrNull() ?: 0.0) - subtrahend
1241+
else -> -subtrahend
1242+
}
1243+
objectMapper.valueToTree(value)
1244+
}
1245+
}
1246+
"@power", "@pow" -> when (json) {
1247+
is ArrayNode -> {
1248+
val exponent = argument?.toDoubleOrNull() ?: 1.0
1249+
val resultArray = objectMapper.createArrayNode()
1250+
json.forEach { node ->
1251+
val value = when {
1252+
node.isNumber -> node.asDouble().pow(exponent)
1253+
node.isTextual -> (node.asText().toDoubleOrNull() ?: 0.0).pow(exponent)
1254+
else -> 0.0
1255+
}
1256+
resultArray.add(value)
1257+
}
1258+
resultArray
1259+
}
1260+
else -> {
1261+
val exponent = argument?.toDoubleOrNull() ?: 1.0
1262+
val value = when {
1263+
json.isNumber -> json.asDouble().pow(exponent)
1264+
json.isTextual -> (json.asText().toDoubleOrNull() ?: 0.0).pow(exponent)
1265+
else -> 0.0
1266+
}
1267+
objectMapper.valueToTree(value)
1268+
}
1269+
}
1270+
"@round" -> when (json) {
1271+
is ArrayNode -> {
1272+
val digits = argument?.toIntOrNull() ?: 0
1273+
val multiplier = 10.0.pow(digits.toDouble())
1274+
val resultArray = objectMapper.createArrayNode()
1275+
json.forEach { node ->
1276+
val value = when {
1277+
node.isNumber -> round(node.asDouble() * multiplier) / multiplier
1278+
node.isTextual -> round((node.asText().toDoubleOrNull() ?: 0.0) * multiplier) / multiplier
1279+
else -> 0.0
1280+
}
1281+
resultArray.add(value)
1282+
}
1283+
resultArray
1284+
}
1285+
else -> {
1286+
val digits = argument?.toIntOrNull() ?: 0
1287+
val multiplier = 10.0.pow(digits.toDouble())
1288+
val value = when {
1289+
json.isNumber -> round(json.asDouble() * multiplier) / multiplier
1290+
json.isTextual -> round((json.asText().toDoubleOrNull() ?: 0.0) * multiplier) / multiplier
1291+
else -> 0.0
1292+
}
1293+
objectMapper.valueToTree(value)
1294+
}
1295+
}
1296+
"@abs" -> when (json) {
1297+
is ArrayNode -> {
1298+
val resultArray = objectMapper.createArrayNode()
1299+
json.forEach { node ->
1300+
val value = when {
1301+
node.isNumber -> abs(node.asDouble())
1302+
node.isTextual -> abs(node.asText().toDoubleOrNull() ?: 0.0)
1303+
else -> 0.0
1304+
}
1305+
resultArray.add(value)
1306+
}
1307+
resultArray
1308+
}
1309+
else -> {
1310+
val value = when {
1311+
json.isNumber -> abs(json.asDouble())
1312+
json.isTextual -> abs(json.asText().toDoubleOrNull() ?: 0.0)
1313+
else -> 0.0
1314+
}
1315+
objectMapper.valueToTree(value)
1316+
}
1317+
}
10831318
else -> json
10841319
}
10851320
}

src/test/kotlin/com/transportial/GSJsonTest.kt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,73 @@ internal object GSJsonTest {
236236
val allFirstNames = GSJson.get(selectJson, "friends.[#.first]|@join")
237237
assertEquals("Dale,Roger,Jane,Sjenkie", allFirstNames, "Should join all first names")
238238
}
239+
240+
@Test
241+
fun testFallbackDefaultValues() {
242+
// Test with existing paths (should return actual values)
243+
val existingAge = GSJson.get(selectJson, "age", 99)
244+
assertEquals(37, existingAge, "Should return actual age, not default")
245+
246+
val existingName = GSJson.get(selectJson, "name.first", "Unknown")
247+
assertEquals("Tom", existingName, "Should return actual name, not default")
248+
249+
// Test with non-existing paths (should return defaults)
250+
val missingField = GSJson.get(selectJson, "nonexistent", "default_value")
251+
assertEquals("default_value", missingField, "Should return default for missing field")
252+
253+
val missingNestedField = GSJson.get(selectJson, "name.middle", "J")
254+
assertEquals("J", missingNestedField, "Should return default for missing nested field")
255+
256+
val missingArrayElement = GSJson.get(selectJson, "friends.[99].name", "Not Found")
257+
assertEquals("Not Found", missingArrayElement, "Should return default for missing array element")
258+
259+
// Test with different data types as defaults
260+
val numericDefault = GSJson.get(selectJson, "missing.number", 42)
261+
assertEquals(42, numericDefault, "Should return numeric default")
262+
263+
val booleanDefault = GSJson.get(selectJson, "missing.boolean", true)
264+
assertEquals(true, booleanDefault, "Should return boolean default")
265+
266+
val listDefault = GSJson.get(selectJson, "missing.list", listOf("a", "b", "c"))
267+
assertEquals(listOf("a", "b", "c"), listDefault, "Should return list default")
268+
}
269+
270+
@Test
271+
fun testMathematicalOperations() {
272+
val mathJson = """
273+
{
274+
"numbers": [10, 20, 30],
275+
"prices": ["1.5", "2.5", "3.0"],
276+
"single": 15,
277+
"negative": [-5, -10, 15]
278+
}
279+
""".trimIndent()
280+
281+
// Test array operations only for now
282+
val multipliedArray = GSJson.get(mathJson, "numbers|@multiply:2")
283+
assertNotEquals(null, multipliedArray, "Should multiply array elements")
284+
285+
val dividedArray = GSJson.get(mathJson, "numbers|@divide:2")
286+
assertNotEquals(null, dividedArray, "Should divide array elements")
287+
288+
val addedArray = GSJson.get(mathJson, "numbers|@add:5")
289+
assertNotEquals(null, addedArray, "Should add to array elements")
290+
291+
val subtractedArray = GSJson.get(mathJson, "numbers|@subtract:5")
292+
assertNotEquals(null, subtractedArray, "Should subtract from array elements")
293+
294+
val absArray = GSJson.get(mathJson, "negative|@abs")
295+
assertNotEquals(null, absArray, "Should get absolute values of array")
296+
297+
val roundedPrices = GSJson.get(mathJson, "prices|@round:0")
298+
assertNotEquals(null, roundedPrices, "Should round array values")
299+
300+
// Chain mathematical operations
301+
val chainedOps = GSJson.get(mathJson, "numbers|@add:10|@multiply:2")
302+
assertNotEquals(null, chainedOps, "Should chain mathematical operations")
303+
304+
// Math with string numbers
305+
val stringMath = GSJson.get(mathJson, "prices|@multiply:2")
306+
assertNotEquals(null, stringMath, "Should perform math on string numbers")
307+
}
239308
}

0 commit comments

Comments
 (0)