-2

So I've recently been tasked with creating a utility which will allow easy addition of JSON to a .json file via the gson library. I've coded this in Kotlin:

fun addObject(filePath: String, name: String, values: Array<Array<String>>) {
try {

    var writer: JsonWriter = JsonWriter(FileWriter(File(filePath)))
    writer.beginObject()
    writer.name(name)
    writer.beginObject()
    for(item in values){
        writer.name(item[0]).value(item[1])
    }
    writer.endObject()
    writer.endObject()

    writer.close()

    println("[JSONUtil] Wrote object successfully!")

} catch (e: IOException) {
    e.printStackTrace()
}
}

I used a 2 Dimensional array to allow the user to add different objects with any number of values in said object. For instance, you would run it like so:

addObject("C:\\Users\\Test\\Desktop\\JsonUtility\\output.json", "GENERAL", 
arrayOf(arrayOf("POS_X","2"), arrayOf("POS_Y","4")))

This creates the following JSON:

{"GENERAL":{"POS_X":"2","POS_Y":"4"}}

This is how it was intended and works, my issue is that upon running the function again it completely overwrites the previous JSON in the file, and this is obviously bad.

My questions are:

  • How can I add new JSON objects inside the entire file, or at specific points, like "addObject("GENERAL", ...)" for this example?

  • How can I make this better?

I'm fairly new to Kotlin and have been coding in Java mostly, so Java solutions are fine as I'm sure I'll be able to convert it.

Thanks in advance!

EDIT: New Code, no idea how to implement it:

fun UpdateJson(path: String, name: String, value: String){
    var gson = Gson()
    var reader: FileReader = FileReader(File(path))

    val type = object : TypeToken<Map<String, String>>() {}.type
    println("Type: " + type.toString())
    println("Existing Json: ${gson.fromJson<Map<String,String>>
    (JsonReader(reader), type)}")
    var existingJson: Map<String, String> = 
    gson.fromJson<Map<String,String>>(JsonReader(reader), type)
    existingJson.put(name, value)
    FileWriter(File(path)).use({ writer -> 
    writer.write(gson.toJson(existingJson)) })
}
CoalCoding
  • 3
  • 1
  • 5

1 Answers1

0

The easiest way is to, as @KrisRoofe suggested, read the json, then add an element. I would do this by converting the existing json in the file to a Map. Since you don't actually care about the existing json, all you want to do is add a new entry to that Map. Once you do that, simply write the new Map to the File. You can do this like so:

public class UpdateJson {
    public static void main(String[] args) throws IOException {
        addObject("example.json", "GENERAL", arrayOf(arrayOf("POS_X","2"), arrayOf("POS_Y","4")));
    }

    private static void addObject(String fileName, String newObjName, String newObjValue) throws IOException {
        Gson gson = new Gson();
        Type type = new TypeToken<Map<String, String>>(){}.getType();
        Map<String, String> existingJson = gson.fromJson(new JsonReader(new FileReader(new File(fileName))), type);
        existingJson.put(newObjName, newObjValue);
        try (FileWriter writer = new FileWriter(new File(fileName))) {
            writer.write(gson.toJson(existingJson));
        }
    }

    private static String arrayOf(String s1, String s2) {
        return "[" + s1 + ", " + s2 + "]";
    }
}

EDIT: The above solution is a Java solution. There seems to be an issue with the Type in Kotlin.

  1. This Stack Overflow question has a workaround by using object:
  2. Also, note that to use reflection with Kotlin, you need a separate jar, according to Kotlin documentation'

EDIT 2: Provided Kotlin Answer:

fun main(args: Array<String>) {
    addObject("example.json", "GENERAL", arrayOf(arrayOf("POS_X", "2"), arrayOf("POS_Y", "4")))
}


fun addObject(path: String, name: String, value: String) {
    val gson = Gson()
    val reader: FileReader = FileReader(File(path))
    val type = object : TypeToken<Map<String, String>>() {}.type
    System.out.println("Type: " + type.toString())
    val existingJson = gson.fromJson<Map<String, String>>(JsonReader(reader), type)
    System.out.println("Existing Json: ${existingJson}")
    val newJsonMap = existingJson.plus(Pair(name, value))
    FileWriter(File(path)).use(
        { writer -> writer.write(gson.toJson(newJsonMap)) }
    )
}

fun arrayOf(s1: String, s2: String): String {
    return "[$s1, $s2]"
}
Ishnark
  • 661
  • 6
  • 14
  • Hmm, having some difficulty with the Type class, seems to not play nice with Kotlin because the Java to Kotlin conversion removes the "Type" type and replaces it with "object", which then results in an inference error. Any alternatives to Type? – CoalCoding Jun 07 '17 at 17:30
  • 1
    just change it back to Type, it works fine in Kotlin. @CoalCoding you are asking people to write your code instead of one specific question. – Jayson Minard Jun 07 '17 at 17:34
  • If I change it back to type it says "unresolved reference "type" at the ".type" it converts it to at the end. ".getType()" is the same. – CoalCoding Jun 07 '17 at 17:36
  • According to the [doc](https://kotlinlang.org/docs/reference/reflection.html), "On the Java platform, the runtime component required for using the reflection features is distributed as a separate JAR file (kotlin-reflect.jar). " – Ishnark Jun 07 '17 at 17:38
  • Will check that out now. The only alternative seems to be "KType", which appears to have a different usage. – CoalCoding Jun 07 '17 at 17:39
  • Also see the solution for [this question](https://stackoverflow.com/questions/33381384/how-to-use-typetoken-generics-with-gson-in-kotlin) – Ishnark Jun 07 '17 at 17:43
  • 1
    Thank you very much. That fixed the issue. – CoalCoding Jun 07 '17 at 17:50
  • Happy to help, and welcome to Stack Overflow. If this answer or any other one solved your issue, please mark it as accepted. – Ishnark Jun 07 '17 at 18:04
  • I'm not done yet unfortunately. I don't really understand how to implement this. I'm sorry I'm a bit of a noob. I've previously been using simple objects and .name and .value and stuff. How do I add stuff to this? How do I combine this with my previous code. – CoalCoding Jun 07 '17 at 18:10
  • Getting this error when I attempt to use: Exception in thread "main" java.lang.IllegalStateException: gson.fromJson – CoalCoding Jun 07 '17 at 18:28
  • Could you add to your post the current version of your code? – Ishnark Jun 07 '17 at 18:36
  • `fun UpdateJson(path: String, name: String, value: String){ var gson = Gson() var reader: FileReader = FileReader(File(path)) val type = object : TypeToken>() {}.type println("Type: " + type.toString()) println("Existing Json: ${gson.fromJson>(JsonReader(reader), type)}") var existingJson: Map = gson.fromJson>(JsonReader(reader), type) existingJson.put(name, value) FileWriter(File(path)).use({ writer -> writer.write(gson.toJson(existingJson)) }) } ` @Ishnark – CoalCoding Jun 07 '17 at 19:11
  • Edited post for Kotlin – Ishnark Jun 07 '17 at 20:03
  • "plus" is not a function and if you try "put" , it requires a string rather than a Pair of strings. Assume you mean this: `val newJsonMap = existingJson.put(name,value)` however that gives you the following error: `Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 13 path $. at com.google.gson.Gson.fromJson(Gson.java:891) at JsonUtilityKt.addObjects(JsonUtility.kt:45) at JsonUtilityKt.main(JsonUtility.kt:15) ` – CoalCoding Jun 08 '17 at 15:10
  • That's interesting -> IntelliJ tells me `put` doesn't exist, but `plus` does. – Ishnark Jun 08 '17 at 15:13
  • We most likely have different versions of gson, I am also using IntelliJ. My version is 2.6.2. – CoalCoding Jun 08 '17 at 15:16
  • `existingJson` is a`Map`. Shouldn't be gson related. Also it looks like `plus` has been in both 1.0.x and 1.1.x versions of the language. Maybe I'm missing something [here's what I'm looking at](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/). Also I'm using `gson-2.8.0.jar` – Ishnark Jun 08 '17 at 15:24
  • Ahh the issue was I was using the Java Map package. I still get this error though: `Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 13 path $.` – CoalCoding Jun 08 '17 at 16:29
  • I got the IllegalStateException by calling `gson.fromJson>(JsonReader(reader), type)` twice. Which is why in my code, instead of calling it twice, the second time I called it (in the print statement) I called it by `existingJson` – Ishnark Jun 08 '17 at 16:34
  • I only used it once and I still get the error with some existing json in there. – CoalCoding Jun 08 '17 at 16:43
  • Also if there is no json in there it is null and doesn't add anything. – CoalCoding Jun 08 '17 at 16:49
  • An empty json file, to be considered an object, must atleast have `{}` – Ishnark Jun 08 '17 at 16:50
  • 1
    That fixed my issue. The error was from using the incorrect format. Everything works now and I realise I've been pretty stupid not reading the gson documentation. Thanks for all the help. – CoalCoding Jun 08 '17 at 16:57