Wrap and move data using only functions and variables (experimental programming) - function

I was programming with kotlin for fun and i tried to make an experiment:
Wrap and move data only using functions (classes and data classes are not allowed)
My first attempt resulted on the code below:
// I've created a function structure that allows me to keep data of a "Person" in memory:
package org.example
typealias InvocableParameter = (String) -> (() -> Any?)?
fun personConstructor(
name: String,
age: Int,
mother: InvocableParameter = { null }): InvocableParameter {
return { parameter: String ->
when (parameter) {
"name" -> {
{ name }
}
"age" -> {
{ age }
}
"mother" -> {
{ mother }
}
"toJson" -> {
{ "{ \"name\": \"$name\", \"age\": $age }" }
}
else -> null
}
}
}
fun main() {
val johnObject = personConstructor(
"john", 20,
personConstructor(
"Lory", 40,
personConstructor("Eva", 60)
)
)
println(johnObject("toJson")?.invoke())
val johnsMother = johnObject("mother")?.invoke() as InvocableParameter
val johnsGrandma = (johnsMother.invoke("mother")?.invoke()) as InvocableParameter
println(johnsMother.invoke("toJson")?.invoke())
println(johnsGrandma.invoke("toJson")?.invoke())
}
// Outputs:
// { "name": "john", "age": 20 }
// { "name": "Lory", "age": 40 }
// { "name": "Eva", "age": 60 }
What are another approaches that I could have used to achieve my initial goal on a more elegant manner?
I've tried to crate a structure that allowed me to store and move data only using functions. I'm expecting to achieve that goal in a smart and elegant way.

Related

Read large (+- 50Mb) Json file using Kotlin

I'm starting to work on a weather app using "openweathermap.org" API, and they provide you with a list of available cities in Json format.
Before i continue with the project, i would like to able to work with the data from this Json file.
The problem is that i get Null whenever i try to read and parse that file.
Here is the code:
Main Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val jsonFileString = getJsonDataFromAsset(applicationContext, "citylist.json")
Log.i("gabs Data", jsonFileString ?: "Empty Data")
val gson = Gson()
val listOfCities = object : TypeToken<List<CityList>>() {}.type
var cities: List<CityList> = gson.fromJson(jsonFileString, listOfCities)
cities.forEachIndexed { idx, city -> Log.i("data", "> Item $idx:\n$city") }
}
}
Utils.kt:
fun getJsonDataFromAsset(context: Context, fileName: String): String? {
val jsonString: String
try {
jsonString = context.assets.open(fileName).bufferedReader().use { it.readText() }
} catch (ioException: IOException) {
ioException.printStackTrace()
return null
}
return jsonString
}
And the data class (Array of cities data):
class CityList : ArrayList<CityList.CityListItem>(){
data class CityListItem(
#SerializedName("coord")
val coord: Coord,
#SerializedName("country")
val country: String,
#SerializedName("id")
val id: Double,
#SerializedName("name")
val name: String,
#SerializedName("state")
val state: String
) {
data class Coord(
#SerializedName("lat")
val lat: Double,
#SerializedName("lon")
val lon: Double
)
}
}
And the error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.weatherdisplay/com.example.weatherdisplay.ui.activities.MainActivity}: java.lang.NullPointerException: gson.fromJson(jsonFileString, listOfCities) must not be null.
Caused by: java.lang.NullPointerException: gson.fromJson(jsonFileString, listOfCities) must not be null
at com.example.weatherdisplay.ui.activities.MainActivity.onCreate(MainActivity.kt:21)
There were some problems in your code:
You were not closing the BufferedReader
You should not load the file on the Main thread since it will block the UI
I created some sample data corresponding to your data structure:
[
{
"id": 1,
"country": "Germany",
"state": "Saxony",
"name": "Dresden",
"coord": {
"lat": 0.0,
"lon": 0.0
}
},
{
"id": 2,
"country": "Germany",
"state": "Berlin",
"name": "Berlin",
"coord": {
"lat": 0.0,
"lon": 0.0
}
},
{
"id": 3,
"country": "Germany",
"state": "Baden-Wuerttemberg",
"name": "Stuttgart",
"coord": {
"lat": 0.0,
"lon": 0.0
}
},
{
"id": 4,
"country": "Germany",
"state": "Hessen",
"name": "Frankfurth",
"coord": {
"lat": 0.0,
"lon": 0.0
}
},
{
"id": 5,
"country": "Germany",
"state": "Nordrhine-Westphalia",
"name": "Cologne",
"coord": {
"lat": 0.0,
"lon": 0.0
}
}
]
Your activity:
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MyApplication"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenStarted {
launch(Dispatchers.IO) {
var reader: BufferedReader? = null
try {
// Create a reader and read the file contents
reader = assets.open("data.json").bufferedReader()
val rawData = reader.use { it.readText() }
// Create a Type token that Gson knows how to parse the raw data
val cityListType = object : TypeToken<List<City>>() {}.type
// Parse the raw data using Gson
val data: List<City> = Gson().fromJson(rawData, cityListType)
// TODO: Do something with the data
} catch (e: IOException) {
// Handle IOException: Gets thrown when the file wasn't found or something similar
Log.e(TAG, "An error occurred while reading in the data:", e)
} catch (e: JsonParseException) {
// Handle JsonParseException: Gets thrown when there is a problem with the contents of the file
Log.e(TAG, "An error occurred while reading in the data:", e)
}
finally {
// Close the reader to release system resources
reader?.close()
}
}
}
}
}
Your data structure:
data class City(
#SerializedName("id")
val id: Int,
#SerializedName("country")
val country: String,
#SerializedName("state")
val state: String,
#SerializedName("name")
val name: String,
#SerializedName("coord")
val coordinate: Coordinate
) {
override fun toString(): String {
return "#$id[$name $state $country]#[${coordinate.lat}|${coordinate.lon}]"
}
}
data class Coordinate(
#SerializedName("lat")
val lat: Double,
#SerializedName("lon")
val lon: Double
)
In the best case you would put the code in which you get the file contents and parse the data in a ViewModel, but this would to go beyond the scope for this answer.
Additional information about ViewModels: https://developer.android.com/topic/libraries/architecture/viewmodel

How to serialize fields with varying type?

I have the following data classes to parse JSON. I can parse it easily with the decodeFromString method. However, the Info classes could contain the List<Int> type from time to time along with the Int type so that both are included in a single JSON. How can I handle this variation in serialization?
#Serializable
data class Node (#SerialName("nodeContent") val nodeContent: List<Info>)
#Serializable
data class Info (#SerialName("info") val info: Int)
p.s. The closest question to mine is this one: Kotlinx Serialization, avoid crashes on other datatype. I wonder if there are other ways?
EDIT:
An example is given below.
"nodeContent": [
{
"info": {
"name": "1",
},
},
{
"info": [
{
"name": "1"
},
{
"name": "2"
},
],
},
{
"info": {
"name": "2",
},
}
]
Here is an approach with a custom serializer similar to the link you provided. The idea is to return a list with just a single element.
// Can delete these two lines, they are only for Kotlin scripts
#file:DependsOn("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0")
#file:CompilerOptions("-Xplugin=/snap/kotlin/current/lib/kotlinx-serialization-compiler-plugin.jar")
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.encoding.Decoder
#Serializable
data class Node (val nodeContent: List<Info>)
#Serializable(with = InfoSerializer::class)
data class Info (val info: List<Name>)
#Serializable
data class Name (val name: Int)
#Serializer(forClass = Info::class)
object InfoSerializer : KSerializer<Info> {
override fun deserialize(decoder: Decoder): Info {
val json = ((decoder as JsonDecoder).decodeJsonElement() as JsonObject)
return Info(parseInfo(json))
}
private fun parseInfo(json: JsonObject): List<Name> {
val info = json["info"] ?: return emptyList()
return try {
listOf(Json.decodeFromString<Name>(info.toString()))
} catch (e: Exception) {
(info as JsonArray).map { Json.decodeFromString<Name>(it.toString()) }
}
}
}
Usage:
val ss2 = """
{
"nodeContent": [
{
"info":
{"name": 1}
},
{
"info": [
{"name": 1},
{"name": 2}
]
},
{
"info":
{"name": 2}
}
]
}
"""
val h = Json.decodeFromString<Node>(ss2)
println(h)
Result:
Node(nodeContent=[Info(info=[Name(name=1)]), Info(info=[Name(name=1), Name(name=2)]), Info(info=[Name(name=2)])])

Scala: Edit/Modify json string based on internal value

I have a json string structured similarly to the following:
val json : String =
{
"identifier":{
"id":"1234_567_910",
"timestamp":"12:34:56",
},
"information":[
{
"fieldName":"test_name",
"fieldId":"test_fieldId",
}
]
}
What I want to do is create a check that verifies the 'id' field matches the structure "Int_Int_Int" and if it doesn't I want to change the value to match this intended structure but I want to keep the rest of the information in the json string as is.
So if I received the following 'id' fields within a json string I would want to change them like so:
"id":"1234_567_910" -> do nothing
"id":"1234" -> "id":"1234_0_0"
"id":"1234_567" -> "id":"1234_567_0"
"id":"1234_???" -> "id":"1234_0_0"
"id":"1234_??_???" -> "id":"1234_0_0"
"id":"1234_foo" -> "id":"1234_0_0"
"id":"1234_567_foo" -> "id":"1234_567_0"
For Example:
If I receive json like this:
{
"identifier":{
"id":"1234",
"timestamp":"12:34:56",
},
"information":[
{
"fieldName":"test_name",
"fieldId":"test_fieldId",
}
]
}
I would want to modify it so I end up with a json like this:
{
"identifier":{
"id":"1234_0_0",
"timestamp":"12:34:56",
},
"information":[
{
"fieldName":"test_name",
"fieldId":"test_fieldId",
}
]
}
What would be the most effective/cleanest way to achieve this type of json modification in Scala?
Below is how it can be done with the Dijon library.
import com.github.pathikrit.dijon._
def normalize(id: String): String =
id.count(_ == '_') match {
case 0 => id + "_0_0"
case 1 => id + "_0"
case _ => id
}
val json =
json"""
{
"identifier":{
"id":"1234",
"timestamp":"12:34:56"
},
"information":[
{
"fieldName":"test_name",
"fieldId":"test_fieldId"
}
]
}"""
json.identifier.id = json.identifier.id.asString.fold("0_0_0")(normalize)
println(pretty(json))
It should print:
{
"identifier": {
"id": "1234_0_0",
"timestamp": "12:34:56"
},
"information": [
{
"fieldName": "test_name",
"fieldId": "test_fieldId"
}
]
}

Unable to create converter for class when using sealed class or an interface with Moshi

I am trying to parse a json data from a server.It has dynamic keys so I am trying to have like a parent class that have the shared keys and child class for each specific node. I wrote a kotlin code using retrofit and Moshi but it's not working. I tried with a sealed class and interface without success. Actually I would prefer that works with sealed class but I don't know what I am doing wrong
interface MyApi {
#GET("/...")
fun fetchMyFeed(): Call<MyResponse>
}
data class MyResponse(
val data: List<ParentResponse>
)
interface ParentResponse{
val name: String
}
data class Child1Response(
val age: String,
val kids: List<KidsResponse>,
val cars: List<CarsResponse>
)
data class Child2Response(
val job: String,
val address: List<AddressResponse>
)
fun fetchAllFeed(): List<Any>? =
try {
val response = api.fetchMyFeed().execute()
if (response.isSuccessful) {
Log.d("check",${response.body()?.data?})
null
} else null
} catch (e: IOException) {
null
} catch (e: RuntimeException) {
null
}```
and the json file is :
{
"data": [
{
"name": "string",
"job": "string",
"address": [
{
"avenue": "string",
"imageUrl": "string",
"description": "string"
}
]
},
{
"name": "string",
"age": "string",
"kids": {
"count": "string",
"working": "string"
},
"cars": [
{
"brand": "string",
"age": "string",
"imageUrl": "string"
}
]
}
]
}
Unable to create converter for class
You can make use of JsonAdapter from moshi to parse different JSON Models if you can differentiate them by foreseeing some value in the json.
for example, consider json response having two schemas,
{
"root": {
"subroot": {
"prop" : "hello",
"type" : "String"
}
}
}
(or)
{
"root": {
"subroot": {
"prop" : 100,
"type" : "Integer"
}
}
}
Here, subroot has different schemas (one containing string property and another containg a integer property) which can be identified by "type"
You can create a parent sealed class with common keys and derive few child classes with varying keys. Write a adapter to select the type of class to be used while json serialization and add that adapter to moshi builder.
Model classes:
class Response {
#Json(name = "root")
val root: Root? = null
}
class Root {
#Json(name = "subroot")
val subroot: HybridModel? = null
}
sealed class HybridModel {
#Json(name = "type")
val type: String? = null
class StringModel : HybridModel() {
#Json(name = "prop")
val prop: String? = null
}
class IntegerModel : HybridModel() {
#Json(name = "prop")
val prop: Int? = null
}
}
Few extension methods to JsonReader,
inline fun JsonReader.readObject(process: () -> Unit) {
beginObject()
while (hasNext()) {
process()
}
endObject()
}
fun JsonReader.skipNameAndValue() {
skipName()
skipValue()
}
HybridAdapter to select type of class for "subroot" key
class HybridAdapter : JsonAdapter<HybridModel>() {
#FromJson
override fun fromJson(reader: JsonReader): HybridModel {
var type: String = ""
// copy reader and foresee type
val copy = reader.peekJson()
copy.readObject {
when (copy.selectName(JsonReader.Options.of("type"))) {
0 -> {
type = copy.nextString()
}
else -> copy.skipNameAndValue()
}
}
//handle exception if type cannot be identified
if (type.isEmpty()) throw JsonDataException("missing type")
// build model based on type
val moshi = Moshi.Builder().build()
return if (type == "String")
moshi.adapter(HybridModel.StringModel::class.java).fromJson(reader)!!
else
moshi.adapter(HybridModel.IntegerModel::class.java).fromJson(reader)!!
}
#ToJson
override fun toJson(p0: JsonWriter, p1: HybridModel?) {
// serialization logic
}
}
Finally build Moshi with the HybridAdapter to serialize HybridModel,
fun printProp(response: Response?) {
val subroot = response?.root?.subroot
when (subroot) {
is HybridModel.StringModel -> println("string model: ${subroot.prop}")
is HybridModel.IntegerModel -> println("Integer model: ${subroot.prop}")
}
}
fun main() {
val jsonWithStringSubroot =
"""
{
"root": {
"subroot": {
"prop" : "hello",
"type" : "String"
}
}
}
"""
val jsonWithIntegerSubroot =
"""
{
"root": {
"subroot": {
"prop" : 1,
"type" : "Integer"
}
}
}
"""
val moshi = Moshi.Builder().add(HybridAdapter()).build()
val response1 = moshi.adapter(Response::class.java).fromJson(jsonWithStringSubroot)
printProp(response1) // contains HybridModel.StringModel
val response2 = moshi.adapter(Response::class.java).fromJson(jsonWithIntegerSubroot)
printProp(response2) // contains HybridModel.IntegerModel
}

How to get Keys and values, while parsing Json using Scala-Play Json Framework?

I have a json file , which has some keys and values. I need to parse the Json and print the keys and their values. For example, the json file is like below. I want to print this as Keys and values
{
"Parcer":[
{
"key":"0203",
"value":{
"Encryption":
{
"enabled":"yes",
"encryption_type":"base64",
"key":"334848484",
"return":"name"
}
}
},
{
"key":"0405",
"value":{
"Encryption":
{
"enabled":"yes",
"encryption_type":"base64",
"key":"334848484",
"return":"none"
},
"Parcer":[
{
"key":"0102",
"value":"humidity"
},
{
"key":"0304",
"value":{
"Encryption":{
"enabled":"yes",
"encryption_type":"SHA1",
"key":"1211212",
"return":"none"
}
}
}
]
}
}],
}```
The easiest way is to create a case class, like:
case class MyObj(header:String, value: Seq[Map[String, String]])
Then you just need to add one line for marshalling, like:
import play.api.libs.json._
object MyObj {
implicit val jsonFormat: OFormat[MyObj] = Json.format[MyObj]
}
Now you get a nice case class that you can work with:
val json =
Json.parse(
"""{
"header" : "header value",
"value" : [
{
"a" : "a_val",
"b" : "b_val",
"c" : "c_val"
},
{
"a" : "a_val",
"b" : "b_val",
"c" : "c_val"
}
]
}""")
Here an example how to retrieve all "a".
json.validate[MyObj] match {
case JsSuccess(myObj, _) =>
val allAs =myObj.value.flatMap(m => m.get("a").toSeq)
println(allAs) // >> Vector(a_val, a_val)
case e:JsError => // handle error
}
This gives you:
json.validate[MyObj] returns JsSuccess(MyObj(header value,Vector(Map(a -> a_val, b -> b_val, c -> c_val), Map(a -> a_val, b -> b_val, c -> c_val))),)
The println returns: Vector(a_val, a_val)
This is described here in the Documentation: JSON automated mapping