Skip to content

ambco-iscte/ktjson

Repository files navigation


ktjson

What is this?

ktjson is a dynamic JSON generation and manipulation library for Kotlin. In short, it allows for the dynamic generation of JSON representations of Kotlin data class instances, or the instantiation of data classes from JSON representations.

This library was developed as the final project for an Advanced Programming course as part of the Master's (MSc) in Computer Engineering programme at Iscte-IUL.

  • Grade: 20 (out of 20)

Features

Phase 1 - Abstract Model, Reflection

  • Abstract JSON model using Kotlin data classes
  • Basic JSON model visitor functionality (Visitor pattern)
  • Serialization of Kotlin objects to abstract JSON models
  • (Extra) Parsing of JSON-formatted text files to abstract JSON models
  • (Extra) Deserialization of abstract JSON models to Kotlin object instances

Phase 2 - Model, View, Controller

  • Listeners for JSON element types implemented in Phase 1
  • Commands to support JSON element actions implemented in Phase 1
  • Graphical User Interface implementing the MVC pattern to edit JSON objects
    • Displaying JSON elements
    • Updating the value of properties in a JSON object
    • Adding and removing properties from JSON objects
    • Adding and removing elements in a JSON array

Examples

The following examples showcase the basic functionality of ktjson's serialization and deserialization capabilities. Check out the documentation for a more in-depth look!

Serialization

More in-depth serialization examples can be found here.

import dynamic.*
import model.*

fun serializationExample() {
    // Some data classes we want to serialize
    data class Student(val name: String, @Stringify val number: Int, @DoNotSerialize val internalID: Int)
    data class Professor(val name: String, val areasOfResearch: List<String>)
    data class CurricularUnit(
        @DoNotSerialize val internalID: String,
        @SerializeAs("name") val identifier: String,
        val coordinator: Professor,
        @SerializeAs("professors") val faculty: Set<Professor>,
        @SerializeAs("students") val studentBody: Set<Student>,
        val program: List<String>
    )

    // A particular instance of one of our data classes
    // Note the inclusion of collection and nested object properties
    val advancedProgramming = CurricularUnit(
        "M4310",
        "PA",
        Professor("André Santos", listOf("Pedagogy", "Programming", "Kotlin")),
        setOf(Professor("Probably ChatGPT", listOf("Everything"))),
        setOf(Student("Afonso", 92494, 0)),
        listOf("Kotlin", "Reflection", "Visitors")
    )

    // Pretty-print (built-in!) of the serialized JSON model
    // Try it out yourself!
    println(advancedProgramming.serialize())
}

Deserialization

More in-depth deserialization examples can be found here.

import dynamic.deserialize
import dynamic.serialize

fun deserializationExample() {
    // Note the absence of annotations - if we want to deserialize later, we can't change or
    // lose any information during the serialization process
    data class Student(val name: String, val number: Int, val studentType: StudentType, val internalID: Int)
    data class Professor(val name: String, val areasOfResearch: List<String>)
    data class CurricularUnit(
        val internalID: String,
        val identifier: String,
        val coordinator: Professor,
        val faculty: Set<Professor>,
        val studentBody: Set<Student>,
        val program: List<String>
    )

    // A particular instance of one of our data classes (same as in the example above)
    val advancedProgramming = CurricularUnit(
        "M4310",
        "PA",
        Professor("André Santos", listOf("Pedagogy", "Programming", "Strudel", "Weird Kotlin Things")),
        setOf(Professor("Probably ChatGPT", listOf("Everything"))),
        setOf(Student("Afonso", 92494, StudentType.INTERNATIONAL, 0)),
        listOf("Kotlin", "Reflection", "Visitors")
    )

    // Check that calling deserialize(thing.serialize()) just returns the same thing
    // We expect this to print "true" to the console. Try it out yourself!
    println(uc == CurricularUnit::class.deserialize(advancedProgramming.serialize()))
}

Parsing

More in-depth parsing examples can be found here.

import model.*

// This is it (it's super easy)
fun parsingExample() {
    val json: JSONObject = JSONElement.parse("myJsonFile.json") // Parse a whole file
    val bool: JSONBoolean = JSONElement.parseLiteral("true") // Parse a literal
}

Manipulation

More in-depth manipulation examples can be found here.

import model.*

fun manipulationExample() {
    // Create an empty JSON object
    val obj = JSONObject.empty()
  
    // Add some properties
    obj.addProperty("name", JSONString("ktjson"))
    obj.addProperty("language", JSONString("kotlin"))
    obj.addProperty("author", JSONString("ambco"))
  
    // Replace a property
    obj.setProperty("author", JSONString("Afonso Caniço"))
  
    // Remove a property
    obj.removeProperty("author")
  
    // Does the object have a property with key "name"?
    println(obj.hasProperty("name"))
}

Visitors

More in-depth visitor examples can be found here.

import model.*
import model.elements.*

// Example visitor: collect all JSON properties that have the same key
class CollectByKey(private val key: String) : JSONVisitor {
    val collected: MutableList<JSONElement> = mutableListOf()
  
    // Override visit() methods for the types of elements you want to run
    // custom code on
    override fun visit(property: JSONProperty) {
      if (property.key == key)
        collected.add(property.value)
    }
  
    // Composite elements (arrays and objects) return True or False depending on
    // whether their "children" (elements or properties) should be visited
    override fun visit(array: JSONArray): Boolean = true
    override fun visit(obj: JSONObject): Boolean = true
}

// Example: load a JSON file and collect all object properties that have key "id"
fun visitorsExample() {
    val collector = CollectByKey("id")
    JSONElement.parse("").accept(collector) // Run the visitor through the object
    println(collector.collected) // Print the list of collected keys
}

Observers / Listeners

More in-depth manipulation examples can be found here.

import model.elements.*
import model.listeners.JSONArrayListener
import model.listeners.JSONObjectListener

// Example: count the number of properties added to, removed from, and updated on an object
fun objectListenerExample() {
    val obj = JSONObject.empty()
  
    var added = 0
    var removed = 0
    var updated = 0
  
    // Attach a listener to the object
    obj.addListener(object : JSONObjectListener {
        // Override the functions of the actions you want to listen to
        override fun onPropertyAdded(property: JSONProperty) { added   }
        override fun onPropertyRemoved(property: JSONProperty) { removed   }
        override fun onPropertyUpdated(old: JSONProperty, new: JSONProperty) { updated   }
    })
  
    // Add, remove, and update some properties (see above) ...
  
    println("Added: $added")
    println("Removed: $removed")
    println("Updated: $updated")
}

// Example: count the number of elements added to, removed from, and set on an array
fun arrayListenerExample() {
    val array = JSONArray(mutableListOf())
  
    var added = 0
    var removed = 0
    var set = 0
  
    // Attach a listener to the array
    array.addListener(object : JSONArrayListener {
        // Override the functions of the actions you want to listen to
        override fun onElementAdded(element: JSONElement) { added   }
        override fun onElementRemoved(index: Int, element: JSONElement) { removed   }
        override fun onElementSet(index: Int, element: JSONElement) { set   }
    })
  
    println("Added: $added")
    println("Removed: $removed")
    println("Set: $set")
}

Using the Editor

ktjson includes a built-in GUI that allows for dynamically editing JSON objects. By using the built-in GUI, you can:

  • Add, change, and remove object properties;
  • Add, change, and remove array elements;
  • See your changes in real-time in the GUI, both in the object structure and in typical JSON text syntax.

Editor Screenshot

The editor includes functionality to allow for listening for changes in the GUI elements and to execute all the JSON manipulation actions as undo-able commands.

A full example on how to make use of the editor's functionalities can be found in EditorExample.kt.


Credit

Full credit for the basic specification of the library's requirements goes to Professor André L. Santos, Assistant Professor at Iscte-IUL and coordinator of the course this library was developed for. The original specifications for each phase of the course's final project can be found here:

Credit for all the code present in this repository goes to Afonso Caniço, author and sole contributor to the project and this repository, unless otherwise explicitly stated. Code heavily inspired by or taken from the Advanced Programming course materials is properly and explicitly identified.