diff --git a/README.md b/README.md index 9914dab..8fa588e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ in the Android SDK. As we all know, those stock classes tend to be a pain. They 1. [Dependency](https://github.com/afollestad/ason#dependency) 1. [Gradle (Java)](https://github.com/afollestad/ason#gradle-java) 2. [Gradle (Android)](https://github.com/afollestad/ason#gradle-android) - 3. [Maven](https://github.com/afollestad/ason#maven) + 3. [Gradle (Kotlin)](https://github.com/afollestad/ason#gradle-kotlin) + 4. [Maven](https://github.com/afollestad/ason#maven) 2. [Parsing and Building Objects](https://github.com/afollestad/ason#parsing-and-building-objects) 3. [Retrieving Values from Objects](https://github.com/afollestad/ason#retrieving-values-from-objects) 4. [Parsing and Building Arrays](https://github.com/afollestad/ason#parsing-and-building-arrays) @@ -52,7 +53,7 @@ The dependency is available via jCenter. ```Gradle dependencies { ... - compile 'com.afollestad:ason:1.3.1' + compile 'com.afollestad:ason:1.4.0' } ``` @@ -63,9 +64,22 @@ Since Android includes `org.json` classes, you'll want to exclude the copies pro ```Gradle dependencies { ... - compile('com.afollestad:ason:1.3.1') { + compile('com.afollestad:ason:1.4.0') { exclude group: 'org.json', module: 'json' - // exclude group: 'com.intellij', module: 'annotations' - Enable this, if you use Kotlin, otherwise you may get a DexException + } +} +``` + +### Gradle (Kotlin) + +In Kotlin, you'll want to exclude IntelliJ's annotations library to avoid a DexException. *If you are using Kotlin with +Android, make sure you also exclude org.json as shown in the section above.* + +```Gradle +dependencies { + ... + compile('com.afollestad:ason:1.4.0') { + exclude group: 'com.intellij', module: 'annotations' } } ``` @@ -76,7 +90,7 @@ dependencies { com.afollestad ason - 1.3.1 + 1.4.0 pom ``` @@ -288,13 +302,24 @@ int day = ason.get("birthday.day"); int year = ason.get("birthday.year"); ``` -You can do the same on arrays, but you need to specify the index of the object to pull from too: +If you wanted to remove the inner "year" value: + +```java +Ason ason = // ... +ason.remove("birthday.year"); +``` + +--- + +You can use dot notation with arrays too, but you need to specify the index of the object to pull from: ```java AsonArray ason = // ... String name = ason.get(1, "birthday.month"); ``` +--- + As a bonus, you can check equality without doing a manual value comparison: ```java @@ -305,22 +330,6 @@ AsonArray ason2 = // ... boolean birthYearCheck2 = ason2.equal(2, "birthday.year", 1995); ``` -And arrays: - -```java -Ason ason = new Ason() - .put("id", 1) - .put("name", "Aidan") - .put("birthday.month", "July") - .put("birthday.day", 28) - .put("birthday.year", 1995); -AsonArray array = AsonArray(); -array.put(ason); - -// The first parameter is the index of the item, the second is a key path, the third is the value you're comparing to -boolean firstItemBirthYearCheck = array.equal(0, "birthday.year", 1995); -``` - ### Index Notation To extend on dot notations in paths, you can use this notation to perform operations on array children. @@ -344,6 +353,23 @@ Take this JSON: } ``` +You could create this using index notation as such: + +```java +Ason ason = new Ason() + .put("group_id", 1) + .put("title", "Hello, world!") + .put("participants.$0.name", "Aidan") + .put("participants.$0.id", 2) + .put("participants.$1.name", "Waverly") + .put("participants.$1.id", 1); +``` + +The dollar sign followed by the number 0 indicates that you want the item at index 0 (position 1) +within an array called "participants". + +--- + You can retrieve the value of "name" in the second participant like this: ```java @@ -351,8 +377,13 @@ Ason object = // ... String name = object.get("participants.$1.name"); ``` -The dollar sign followed by the number 1 indicates that you want the item at index 1 (position 2) -within the array called "participants". +If you wanted to remove the first item from the inner array, you can do that with index notation. This avoids the +need to first retrieve the "participants" object: + +```java +Ason object = // ... +object.remove("participants.$0"); +``` ### Escaping Periods and Dollar Signs diff --git a/build.gradle b/build.gradle index 4627ac4..2ba85d0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'com.afollestad' -version '1.3.1' +version '1.4.0' apply plugin: 'java' apply plugin: 'idea' @@ -32,7 +32,7 @@ publish { userOrg = 'drummer-aidan' groupId = 'com.afollestad' artifactId = 'ason' - publishVersion = '1.3.1' + publishVersion = '1.4.0' website = 'https://github.com/afollestad/ason' } diff --git a/src/main/java/com/afollestad/ason/Ason.java b/src/main/java/com/afollestad/ason/Ason.java index b6f9449..9588a7f 100644 --- a/src/main/java/com/afollestad/ason/Ason.java +++ b/src/main/java/com/afollestad/ason/Ason.java @@ -95,8 +95,19 @@ public Ason put(@NotNull String key, Object... values) { } if (key.contains(".")) { final String[] splitKey = splitPath(key); - JSONObject target = followPath(json, key, splitKey, true); - target.put(splitKey[splitKey.length - 1], insertObject); + Object target = followPath(json, key, splitKey, true); + if (target instanceof JSONArray) { + JSONArray arrayTarget = (JSONArray) target; + String indexKey = splitKey[splitKey.length - 1].substring(1); + int insertIndex = Integer.parseInt(indexKey); + if (insertIndex > arrayTarget.length() - 1) { + arrayTarget.put(insertObject); + } else { + arrayTarget.put(insertIndex, insertObject); + } + } else { + ((JSONObject) target).put(splitKey[splitKey.length - 1], insertObject); + } } else { putInternal(null, null, key, insertObject); } @@ -104,7 +115,19 @@ public Ason put(@NotNull String key, Object... values) { } public Ason remove(@NotNull String key) { - json.remove(key); + String[] splitKey = splitPath(key); + if (splitKey.length == 1) { + json.remove(key); + } else { + Object followed = followPath(json, key, splitKey, false); + if (followed instanceof JSONArray) { + JSONArray followedArray = (JSONArray) followed; + int insertIndex = Integer.parseInt(splitKey[splitKey.length - 1].substring(1)); + followedArray.remove(insertIndex); + } else { + ((JSONObject) followed).remove(splitKey[splitKey.length - 1]); + } + } return this; } diff --git a/src/main/java/com/afollestad/ason/Util.java b/src/main/java/com/afollestad/ason/Util.java index f5bb89f..829202e 100644 --- a/src/main/java/com/afollestad/ason/Util.java +++ b/src/main/java/com/afollestad/ason/Util.java @@ -1,5 +1,6 @@ package com.afollestad.ason; +import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONObject; @@ -35,7 +36,7 @@ static String[] splitPath(String key) { return result.toArray(new String[result.size()]); } - private static boolean isNumber(String string) { + static boolean isNumber(String string) { for (char c : string.toCharArray()) { if (!Character.isDigit(c)) { return false; @@ -44,10 +45,11 @@ private static boolean isNumber(String string) { return true; } - static JSONObject followPath(JSONObject wrapper, - String key, - String[] splitKey, - boolean createMissing) { + @NotNull static Object followPath( + JSONObject wrapper, + String key, + String[] splitKey, + boolean createMissing) { // Get value for the first path key Object parent = wrapper.opt(splitKey[0]); if (parent != null @@ -58,7 +60,13 @@ static JSONObject followPath(JSONObject wrapper, parent.getClass().getName() + ")."); } else if (parent == null) { if (createMissing) { - parent = new JSONObject(); + if (splitKey[0].startsWith("$") + || (splitKey.length > 1 + && splitKey[1].startsWith("$"))) { + parent = new JSONArray(); + } else { + parent = new JSONObject(); + } wrapper.put(splitKey[0], parent); } else { throw new InvalidPathException("No object or array found for the first component of key " + @@ -85,8 +93,14 @@ static JSONObject followPath(JSONObject wrapper, current.getClass().getName() + ")."); } else if (current == null) { if (createMissing) { - current = new JSONObject(); - ((JSONObject) parent).put(currentKey, current); + if (i < splitKey.length - 1 + && splitKey[i + 1].startsWith("$")) { + current = new JSONArray(); + ((JSONArray) parent).put(current); + } else { + current = new JSONObject(); + ((JSONArray) parent).put(current); + } } else { throw new NullPathException("Item at index " + i + " " + "of current entry refers to a null or out of bounds entry."); @@ -107,7 +121,12 @@ static JSONObject followPath(JSONObject wrapper, current.getClass().getName() + ")."); } else if (current == null) { if (createMissing) { - current = new JSONObject(); + if (i < splitKey.length - 1 + && splitKey[i + 1].startsWith("$")) { + current = new JSONArray(); + } else { + current = new JSONObject(); + } ((JSONObject) parent).put(currentKey, current); } else { throw new NullPathException("Item at index " + i + " " + @@ -117,7 +136,7 @@ static JSONObject followPath(JSONObject wrapper, parent = current; } - return (JSONObject) parent; + return parent; } @SuppressWarnings("unchecked") static T getPathValue( @@ -127,8 +146,12 @@ static JSONObject followPath(JSONObject wrapper, if (splitKey.length == 1) { return (T) wrapper.get(key); } - JSONObject target = followPath(wrapper, key, splitKey, false); - return (T) target.opt(splitKey[splitKey.length - 1]); + Object target = followPath(wrapper, key, splitKey, false); + if (target instanceof JSONObject) { + return (T) ((JSONObject) target).opt(splitKey[splitKey.length - 1]); + } else { + throw new InvalidPathException("Cannot get a value from a JSONArray using a key."); + } } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/afollestad/ason/AsonPathTest.java b/src/test/java/com/afollestad/ason/AsonPathTest.java index f5c06c5..bf73938 100644 --- a/src/test/java/com/afollestad/ason/AsonPathTest.java +++ b/src/test/java/com/afollestad/ason/AsonPathTest.java @@ -7,6 +7,11 @@ public class AsonPathTest { + @Test public void test_split_path_no_components() { + String[] result = Util.splitPath("Hello!"); + assertEquals(1, result.length); + } + @Test public void builder_test() { Ason ason = new Ason() .put("person._id", 3) @@ -21,6 +26,69 @@ public class AsonPathTest { assertEquals(output, ason.toString()); } + @Test public void builder_index_test_one() { + Ason ason = new Ason() + .put("_id", 3) + .put("name", "Aidan") + .put("pets.$0", "Kierra") + .put("pets.$1", "Elijah") + .put("pets.$2", "Olivia"); + assertEquals("{\"pets\":" + + "[\"Kierra\",\"Elijah\",\"Olivia\"]," + + "\"name\":\"Aidan\",\"_id\":3}", ason.toString()); + } + + @Test public void builder_index_test_two() { + Ason ason = new Ason() + .put("_id", 3) + .put("name", "Aidan") + .put("pets.$0.id", 1) + .put("pets.$0.name", "Kierra") + .put("pets.$1.id", 2) + .put("pets.$1.name", "Elijah") + .put("pets.$2.id", 3) + .put("pets.$2.name", "Olivia"); + assertEquals("{\"pets\":[" + + "{\"name\":\"Kierra\",\"id\":1}," + + "{\"name\":\"Elijah\",\"id\":2}," + + "{\"name\":\"Olivia\",\"id\":3}]," + + "\"name\":\"Aidan\",\"_id\":3}", ason.toString()); + } + + @Test public void builder_index_test_three() { + Ason ason = new Ason() + .put("_id", 1) + .put("people.$0.name", "Aidan") + .put("people.$0.pets.$0", "Kierra") + .put("people.$0.pets.$1", "Elijah") + .put("people.$0.pets.$2", "Olivia"); + assertEquals("{\"_id\":1," + + "\"people\":[" + + "{\"pets\":" + + "[\"Kierra\",\"Elijah\",\"Olivia\"]," + + "\"name\":\"Aidan\"}" + + "]" + + "}", ason.toString()); + } + + @Test public void builder_index_test_four() { + Ason ason = new Ason() + .put("_id", 1) + .put("people.$0.name", "Aidan") + .put("people.$0.id", 1) + .put("people.$0.pets.$0.name", "Kierra") + .put("people.$0.pets.$0.id", 1) + .put("people.$0.pets.$1.name", "Elijah") + .put("people.$0.pets.$1.id", 2); + assertEquals("{\"_id\":1," + + "\"people\":[" + + "{\"pets\":" + + "[{\"name\":\"Kierra\",\"id\":1}," + + "{\"name\":\"Elijah\",\"id\":2}]," + + "\"name\":\"Aidan\"," + + "\"id\":1}]}", ason.toString()); + } + @Test public void from_string_test() { String input = "{\"person\":{\"name\":\"Aidan\",\"_id\":3,\"age\":21}}"; Ason ason = new Ason(input); @@ -78,4 +146,29 @@ public class AsonPathTest { Ason object = new Ason(input); assertEquals("Waverly", object.get("participants.\\$1.name")); } + + @Test public void test_remove_dot_notation() { + Ason ason = new Ason() + .put("_id", 3) + .put("name", "Aidan") + .put("age", 21) + .put("spouse.name", "Waverly") + .put("spouse.age", 19); + ason.remove("spouse.age"); + assertEquals("{\"name\":\"Aidan\",\"_id\":3,\"age\":21," + + "\"spouse\":{\"name\":\"Waverly\"}}", ason.toString()); + } + + @Test public void test_remove_index_notation() { + String input = "{\"group_id\":1,\"title\":\"Hello, world!\"," + + "\"participants\":[{\"name\":\"Aidan\",\"id\":2}," + + "{\"name\":\"Waverly\",\"id\":1}]}"; + Ason object = new Ason(input); + object.remove("participants.$0"); + + AsonArray participants = object.get("participants"); + assertEquals(participants.size(), 1); + assertEquals(participants.get(0).get("id"), 1); + assertEquals(participants.get(0).get("name"), "Waverly"); + } } diff --git a/src/test/java/com/afollestad/ason/AsonTest.java b/src/test/java/com/afollestad/ason/AsonTest.java index 986f855..0226bb1 100644 --- a/src/test/java/com/afollestad/ason/AsonTest.java +++ b/src/test/java/com/afollestad/ason/AsonTest.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Map; +import static com.afollestad.ason.Util.isNumber; import static org.junit.Assert.*; public class AsonTest { @@ -17,6 +18,17 @@ public class AsonTest { } } + @Test public void test_is_number_true() { + assertTrue(isNumber("1234")); + assertTrue(isNumber("67891023231")); + } + + @Test public void test_is_number_false() { + assertFalse(isNumber("hi")); + assertFalse(isNumber("@1234")); + assertFalse(isNumber("1234!%")); + } + @Test public void builder_test() { Ason ason = new Ason() .put("_id", 3) @@ -47,7 +59,7 @@ public class AsonTest { @Test public void test_is_null() { Ason ason = new Ason() .put("_id", 3) - .put("name", null) + .put("name", (Object[]) null) .put("age", 21); assertTrue(ason.isNull("name")); assertFalse(ason.isNull("age"));