Migrating Python json to Kotlin JVM?

#1

Hello
I have existing Python programs which use import json and use json.load() json.loads() methods to read the json and present it in Python code in easy to use accessible dictionary.

I use python json[name] and json[name] = value constructs to reference the json data in memory and also use the same type of syntax to assign new values to json elements.

I want to migrate the Python code to Kotlin JVM (server-side, Windows and Linux) and looking for JSON libraries which will do something close to what Python json.load() etc do in Kotlin.

I did use Google but did not find anything, there are plenty of Kotlin/Java JSON libraries but I dont think they offer anything like Python JSON load methods.

What is my best option library to use? (cross posted on Stack Overflow)

Thank you

1 Like
#2
implementation 'org.json:json:20180813'

Next time you want any libraries, just search for its java version, kotlin is JVM-compatible with java.

And you can extend operator set() and operator get() methods on JSONObject to get json[name] and json[name] = value syntax. Indeed you can do this to any class you wish.

1 Like
#3

Besides, if you want to ease your json read/write, why not make an object mapping tool?

abstract class JsonProcessor {
    inline fun <reified T: Any> read(json: String): T = read(json, T::class)
    inline fun <reified T: Any> write(task: T): String = write(task, T::class)
    abstract fun <T: Any> read(json: String, clazz: KClass<T>): T
    abstract fun <T: Any> write(task: T, clazz:KClass<T>): String
}
object JsonProcessorImpl : JsonProcessor() {
	private enum class SupportedTypes(val clazz: KClass<*>) {
		Boolean(kotlin.Boolean::class),
		Int(kotlin.Int::class),
		Long(kotlin.Long::class),
		String(kotlin.String::class),
		Double(kotlin.Double::class),
		URI(java.net.URI::class);
		companion object {
			private val map by lazy { values().map { it.clazz.createType() to it }.toMap() }
			operator fun get(type: KType) = map[type]
		}
	}
	override fun <T: Any> write(task: T, clazz: KClass<T>) = writeJson(task, clazz).toString()
	private fun <T: Any> writeJson(task: T, clazz: KClass<T>): JSONObject {
		if (clazz.isData.not()) throw IllegalArgumentException("Class ${clazz.simpleName} should be a data class")
		val json = JSONObject()
		clazz.memberProperties.forEach {
			when (SupportedTypes[it.returnType]) {
				JsonProcessorImpl.SupportedTypes.Boolean -> json.put(it.name, it.get(task) as Boolean)
				JsonProcessorImpl.SupportedTypes.Int -> json.put(it.name, it.get(task) as Int)
				JsonProcessorImpl.SupportedTypes.Long -> json.put(it.name, it.get(task) as Long)
				JsonProcessorImpl.SupportedTypes.String -> json.put(it.name, it.get(task) as String)
				JsonProcessorImpl.SupportedTypes.Double -> json.put(it.name, it.get(task) as Double)
				JsonProcessorImpl.SupportedTypes.URI -> json.put(it.name, (it.get(task) as URI).toString())
				else -> json.put(it.name, it.get(task)!!.let { objectProperty ->
					writeJson(objectProperty, objectProperty.javaClass.kotlin) })
			}
		}
		return json
	}
	override fun <T: Any> read(json: String, clazz: KClass<T>) =
		readJson(JSONObject(json), clazz)
	private fun <T: Any> readJson(jsonObject: JSONObject, clazz: KClass<T>): T {
		if (clazz.isData.not()) throw IllegalArgumentException("Class ${clazz.simpleName} should be a data class")
		return clazz.primaryConstructor!!.parameters.map { it to when (SupportedTypes[it.type]) {
			JsonProcessorImpl.SupportedTypes.Boolean -> jsonObject.getBoolean(it.name!!)
			JsonProcessorImpl.SupportedTypes.Int -> jsonObject.getInt(it.name!!)
			JsonProcessorImpl.SupportedTypes.Long -> jsonObject.getLong(it.name!!)
			JsonProcessorImpl.SupportedTypes.String -> jsonObject.getString(it.name!!)
			JsonProcessorImpl.SupportedTypes.Double -> jsonObject.getDouble(it.name!!)
			JsonProcessorImpl.SupportedTypes.URI -> URI(jsonObject.getString(it.name!!))
			else -> readJson(
				jsonObject.getJSONObject(it.name!!),
				getClassByType(it.type)
			)
		} }.toMap().let { clazz.primaryConstructor!!.callBy(it) }
	}
	private fun getClassByType(type: KType): KClass<*> {
		val name = type.toString()
		return try {
			Class.forName(name)
		} catch (_: Throwable) {
			Class.forName("${name}Kt")
		}.kotlin
	}
}

Usage sample:

fun main() {
    data class Stub(val aString: String, val int1: Int, val int2: Int)
    val obj = Stub(aString = "aString value", int1 = 10, int2 = 20)
    val json = JsonProcessorImpl.write(obj)
    println(json)
    val restoredObj = JsonProcessorImpl.read<Stub>(json)
    println(restoredObj)
} 

Here is its output:

{“int2”:20,“int1”:10,“aString”:“aString value”}
Stub(aString=aString value, int1=10, int2=20)

1 Like
#4

Thank you. I was hoping much of what I do in json in python is already available in kotlin too.

Let me give you a sample, perhaps you can lead me here, I am totally new to Kotlin.
Below are snippets of python code.
// start of Python sample code below
import json
js = json.loads(response[‘Body’].read()) # read json from AWS S3
# Contents of js document read is this:
{
“environment”: “production”,
“postgres_host” : “pgstaging-prod.blah.blah.rds.amazonaws.com”,
“postgres_port” : 5432,
“postgres_database” : “datawarehouse”,
“postgres_user” : “pguser”,
“postgres_password” : “!!!”,
“postgres_config_schema” : “etl_operations”,
“postgres_validation_log_table_name” : “jobs_process_log”,
“postgres_destination_table_name” : “myleads”,
“debugFlag”: true,
“configBucket”: “csn-datalake”,
“configFileName”: “yurib_test/myleads.json”,
“HighWaterMarkFileName”: “yurib_test/dts_high_watermark.json”,
“repartitionSourceTableName”: “leads”,
“repartitionSourceDbName”: “datalake”,
“sourceTablePartitionDateTime”: “job_run_timestamp_utc”,
“repartitionTargetTableName”: “outleads”,
“repartitionTargetDbName”: “datalake_reports”,
“repartitionTargetS3Location”: “yurib_test/outleads”,
“repartitionTargetColumnList”: [
{“colName”: “message_item_countrycode”, “isDatePartition”: false, “renameTo” : “message_countrycode”},
{“colName”: “message_tenant_code”, “isDatePartition”: false, “renameTo” : “message_tenant”},
{“colName”: “message_lastupdated”, “isDatePartition”: true, “renameTo” : “message_lastupdated”}
],
“repartitionLoadType”: “incremental_data_load”,
“repartition”: 4,
“repartitionLoadTypeIncremental”: “incremental_data_load”,
“repartitionLoadTypeFullInitial”: “full_data_load”,
“countryCodeColName”: “message_item_countrycode”,
“tenantColName”: “message_tenant_code”,
“autoCreateCountryTenant”: false,
“autoCreateCountry”: true,
“autoCreateTenant”: true,
“partitionDateDefault”: “0000-00-00”,
“partitionYearDefault”: “0000”,
“partitionMonthDefault”: “00”,
“partitionDayDefault”: “00”,
“countryCodeDefault”: “AU”,
“tenantDefault”: “CARSALES”,
“missingPartColDataValReplace”: “MISSINGDATA”,
“validateIncomingCountryTenant”: true,
“datePartitionStyle”: “ym”,
“datePartitionStyleYearMonth”: “ym”,
“datePartitionStyleYearMonthDay”: “ymd”,
“propagateGlueJobRunGuid”: false
}
# here is how Python can access above json document using and range

    print (js["repartitionLoadType"])
    print (js['configBucket'], js['configFileName'], js['HighWaterMarkFileName'])
    print (js['repartitionTargetTableName'], js['repartitionTargetS3Location'])
    print (js['repartitionSourceTableName'], js['repartitionSourceDbName'])
    print (js['repartitionTargetDbName'], js['repartitionLoadType'])
    print (js['autoCreateCountry'], js['autoCreateTenant'], js['missingPartColDataValReplace'])
    print (js['countryCodeColName'], js['tenantColName'], js['propagateGlueJobRunGuid'])
    print (js['countryCodeDefault'], js['tenantDefault'], js['validateIncomingCountryTenant'], js['repartition'])

    partition_dts = ""
    # json array
    for i in range(0, len(js['repartitionTargetColumnList'])):
        if True == js['repartitionTargetColumnList'][i]['isDatePartition']:
            partition_dts = "`" + js['repartitionTargetColumnList'][i]['colName'] + "`"
        else:
            js['repartitionTargetColumnList'][i]['colName'] = "new value replace/assign here"
        continue

# to set/replace/assign any values above:
    js["repartitionLoadType"] = "some new value"

– end of python code

So I was looking for direction of how to tackle this in Kotlin and what JSON library I can use so I dont have to re-invent the wheel.

#5

The big difference between Python and Kotlin/Java is Type Safty.
So in Python you can access value from JSON regardless if it is String, number, boolean, array or substructure. If you using value of the wrong type you will have a runtime error.
Java and Kotlin try to help you not to do such mistakes, so handling values from JSON will be a bit more complicated than in Python, but less likely to break if successfully compiled.

1 Like