kmongo connot save kotlinx-datetime objects - kotlin

I am trying to use kmongo with kotlinx-datetime. The data class looks like this:
#Serializable
data class CustomerCount(
#Contextual val _id: Id<CustomerCount>?,
val counter: Int,
#Contextual val date: LocalDateTime
) {
constructor(counter: Int): this(
null,
counter,
Clock.System.now().toLocalDateTime(TimeZone.of("Europe/Zurich"))
)
}
When I now try to save an instance of CustomerCount to MongoDB I get the following error:
com.mongodb.MongoWriteException: Write operation error on server localhost:27017. Write error: WriteError{code=2, message=''date' must be present and contain a valid BSON UTC datetime value', details={}}.
How can I save kotlinx-datetime objects to MongoDB?

Related

How can I convert a data class to a room entity class and a UI class?

I'm pulling values ​​like conversion_rates from the exchange api and I don't want to use all of these values. Because there are data that I do not want to use in the data coming from the api. In addition, I want to save the data I want to use in the room and perform operations such as reading and updating from there. But I couldn't find how to convert the data class to room entity and the data class I want to show in ui.
The data in the api is as follows, for example. This example uses USD currency
{
"result":"success",
"documentation":"https://www.exchangerate-api.com/docs",
"terms_of_use":"https://www.exchangerate-api.com/terms",
"time_last_update_unix":1669852801,
"time_last_update_utc":"Thu, 01 Dec 2022 00:00:01 +0000",
"time_next_update_unix":1669939201,
"time_next_update_utc":"Fri, 02 Dec 2022 00:00:01 +0000",
"base_code":"USD",
"conversion_rates":{
"USD":1,
"AED":3.6725,
"AFN":88.4843,
"ALL":112.6064,
"AMD":395.1203,
"ANG":1.7900,
"AOA":509.9729,
"ARS":166.6930,
"AUD":1.4810,
"AWG":1.7900,
"AZN":1.6987,
"BAM":1.8843,
"BBD":2.0000,
"BDT":101.1341,
"BGN":1.8844,
"BHD":0.3760,
"BIF":2059.0872,
"BMD":1.0000,
"BND":1.3652,
"BOB":6.9223,
"BRL":5.2810,
"BSD":1.0000,
"BTN":81.2806,
"BWP":12.8706,
"BYN":2.8013,
"BZD":2.0000,
"CAD":1.3488,
"CDF":2054.0886,
"CHF":0.9472,
"CLP":900.6901,
"CNY":7.0769,
"COP":4807.5892,
"CRC":599.5200,
"CUP":24.0000,
"CVE":106.2304,
"CZK":23.4794,
"DJF":177.7210,
"DKK":7.1874,
"DOP":54.5873,
"DZD":138.5566,
"EGP":24.5835,
"ERN":15.0000,
"ETB":53.4589,
"EUR":0.9631,
"FJD":2.2145,
"FKP":0.8319,
"FOK":7.1874,
"GBP":0.8317,
"GEL":2.7155,
"GGP":0.8319,
"GHS":14.2807,
"GIP":0.8319,
"GMD":63.3901,
"GNF":8622.0204,
"GTQ":7.8226,
"GYD":209.1401,
"HKD":7.8054,
"HNL":24.6994,
"HRK":7.2588,
"HTG":140.6488,
"HUF":393.5594,
"IDR":15633.2624,
"ILS":3.4355,
"IMP":0.8319,
"INR":81.2818,
"IQD":1458.8962,
"IRR":42024.7114,
"ISK":141.8861,
"JEP":0.8319,
"JMD":154.0163,
"JOD":0.7090,
"JPY":138.2686,
"KES":122.8172,
"KGS":84.4061,
"KHR":4125.2930,
"KID":1.4813,
"KMF":473.9665,
"KRW":1311.7736,
"KWD":0.2996,
"KYD":0.8333,
"KZT":469.2553,
"LAK":17329.4289,
"LBP":1507.5000,
"LKR":363.3087,
"LRD":153.9036,
"LSL":17.0637,
"LYD":4.8894,
"MAD":10.7013,
"MDL":19.4034,
"MGA":4347.5707,
"MKD":59.4442,
"MMK":2452.0310,
"MNT":3422.5815,
"MOP":8.0398,
"MRU":38.0126,
"MUR":43.6263,
"MVR":15.4107,
"MWK":1029.7547,
"MXN":19.2883,
"MYR":4.4641,
"MZN":64.0629,
"NAD":17.0637,
"NGN":443.7037,
"NIO":36.3802,
"NOK":9.8760,
"NPR":130.0490,
"NZD":1.5947,
"OMR":0.3845,
"PAB":1.0000,
"PEN":3.8417,
"PGK":3.5202,
"PHP":56.3904,
"PKR":223.9704,
"PLN":4.4951,
"PYG":7228.9088,
"QAR":3.6400,
"RON":4.7382,
"RSD":113.3592,
"RUB":60.9665,
"RWF":1099.6940,
"SAR":3.7500,
"SBD":8.1340,
"SCR":13.0123,
"SDG":568.2933,
"SEK":10.5247,
"SGD":1.3649,
"SHP":0.8319,
"SLE":18.6210,
"SLL":18620.9957,
"SOS":568.2695,
"SRD":30.9539,
"SSP":643.6471,
"STN":23.6035,
"SYP":2502.5362,
"SZL":17.0637,
"THB":35.2322,
"TJS":10.1162,
"TMT":3.4979,
"TND":3.1050,
"TOP":2.3745,
"TRY":18.6339,
"TTD":6.7542,
"TVD":1.4813,
"TWD":30.6481,
"TZS":2333.7110,
"UAH":36.4908,
"UGX":3747.1520,
"UYU":39.4702,
"UZS":11228.0193,
"VES":11.0789,
"VND":24625.3969,
"VUV":121.1513,
"WST":2.7337,
"XAF":631.9553,
"XCD":2.7000,
"XDR":0.7612,
"XOF":631.9553,
"XPF":114.9656,
"YER":250.0464,
"ZAR":17.0568,
"ZMW":17.0409,
"ZWL":653.4299
}
}
but here, for example, I don't want to save
time_next_update_utc,
"time_next_update_unix",
"time_last_update_utc,
time_last_update_unix,
terms_of_use",
documentation
data to the room, and I don't want to show them to the user either.
because data is useless to me. I just want to save the following data in the room and show it to the user
"result",
"base_code",
"conversion_rates"
and I prepared something like that.
Room
Entity
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "allData") val allData: AllData,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
Dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET ")
suspend fun update()
}
Remote
Dto
#Serializable
data class AllData(
val allData:List<ExchangeDto>
)
#Serializable
data class ExchangeDto(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
)
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
In this Dto, I wrote extension functions such as toEntity to convert it to an entity or to convert it to a model class that I will display ui, but I couldn't make it up. I'm a little lacking in this
Repository
interface ExchangeRepository {
suspend fun getAll() : Flow<List<AllData>>
suspend fun update(exchange: ExchangeDto)
}
RepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<AllData>> {
return flow {
emit(dao.getAll().map {it.allData}) // this part is wrong
}
}
override suspend fun update(exchange: ExchangeDto) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates) // While updating, it is important that it is compatible with my entity class. There is also a problem here.
}
}
I guess I didn't prepare the dto properly. Actually, I'm sorry that I may have shared the codes a bit incompletely and badly because I don't know exactly what to do, but I hope you understand what I want to do.
The problem is when I getAll in the ExchangeRepositoryImpl , the ExchangeEntity is constantly coming, and I can't translate it either. At the same time, I couldn't quite figure out how to adapt the update in ExchangeRepositoryImpl to them. because i need to update data every 24 hours according to data in api.

Kotlin filter list by predicate

I am trying to filter a list based on a condition that a property inside the list is an enum type. But I get an error on the filter function. Can anyone tell me how to resolve this error and why it is happening?
Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.
My code is below:
data class Person(
val name: String,
val ageInDays: Int,
val currentStatus: List<Status>,
)
data class Status(
val name: String,
val activity: Activity
)
enum class Activity {
COOK,
CLEAN,
SLEEP,
}
fun main() {
var build = listOf(
Person("abc", 3655, listOf(
Status("abcProc1", Activity.COOK),
Status("abcProc2", Activity.CLEAN),
Status("abcProc2", Activity.SLEEP),
)
),
Person("ghi", 500, listOf(
Status("ghiProc", Activity.COOK),
Status("ghiProc", Activity.SLEEP),
)
),
Person("def", 1000,listOf(
Status("defProc", Activity.SLEEP)
)
)
)
println(build.filter { it.currentStatus.contains(Activity.CLEAN) })
}
currentStatus is a List<Status>. The only type of object that could be in the list is a Status (or subtype thereof). So it doesn't make sense to call contains on the list with an argument that is not a Status. An Activity is not a subtype of Status.
Assuming you want to filter your list of Status to only include instances of Status for which the activity property is Activity.CLEAN, you would do it like:
build.filter { it.currentStatus.any { status -> status.activity == Activity.CLEAN } }
or slightly less efficient but possibly clearer logic:
build.filter { it.currentStatus.map(Status::activity).contains(Activity.CLEAN) }

Android Room Embedded Field Not Compiling

I am trying to create an embedded field. This is a simple example but I can't get this simple example to work. Eventually I need to have 3 levels of embedded items but trying to get this test case to work.
#Entity(tableName = "userItemsEntity")
#Parcelize
data class Item(
var objecttype: String?,
#PrimaryKey(autoGenerate = false)
var objectid: Int?,
var subtype: String?,
var collid: Int?,
#Embedded
var name: Name?
) : Parcelable
#Parcelize
data class Name(
var primary: Boolean? = true,
var sortindex: Int? = null,
var content: String? = null) : Parcelable
When I try and compile it it complains on the DAO that the updateItem()
SQL error or missing database (no such column: name)
DAO function
#Query("UPDATE userItemsEntity SET " +
"objecttype=:objecttype, objectid=:objectid, subtype=:subtype, collid=:collid, name=:name " +
"WHERE objectid=:objectid")
fun updateItem(
objecttype: String?,
objectid: Int,
subtype: String?,
collid: Int?,
name: Name?)
The reason is as it says there is no name column. Rather the table consists of the columns, as per the member variables of the EMBEDDED class (i.e. primary, sortindex and content).
i.e. the table create SQL is/will be :-
CREATE TABLE IF NOT EXISTS `userItemsEntity` (`objecttype` TEXT, `objectid` INTEGER, `subtype` TEXT, `collid` INTEGER, `primary` INTEGER, `sortindex` INTEGER, `content` TEXT, PRIMARY KEY(`objectid`))
Room knows to build the respective Name object from those columns when extracting rows.
So you could use :-
#Query("UPDATE userItemsEntity SET " +
"objecttype=:objecttype, objectid=:objectid, subtype=:subtype, collid=:collid, `primary`=:primary, sortindex=:sortindex, content=:content " +
"WHERE objectid=:objectid")
fun updateItem(
objecttype: String?,
objectid: Int,
subtype: String?,
collid: Int?,
primary: Boolean?,
sortindex: Int?,
content: String?
)
note that primary is an SQLite token and thus enclosed in grave accents to ensure that it is not treated as a token. Otherwise you would get :-
There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (near "primary": syntax error)
However, as you are using a WHERE clause based upon the primary key (objectid) then the update will only apply to a single row and as such you can simply use:-
#Update
fun update(item: Item): Int
obviously the function's name need not be update it could be any valid name that suits.
this has the advantage of not only being simpler but of returning the number of rows updated (would be 1 if the row exists, otherwise 0)
Impementing a name column
If you want a name column and for that name column to hold a Name object. Then, as SQLite does not have storage/column types for objects then you would not EMBED the Name class.
You would have var name: Name? with an appropriate TypeConverter that would convert the Name object into a type that SQLite caters for :-
TEXT (String),
REAL (Float, Double...),
INTEGER (Long, Int ...) or
BLOB (ByteArray)).
Typically String is used and typically GSON is used to convert from an object to a JOSN String.
SQlite does have a NUMERIC type. However, Room doesn't support it's use. I believe because the other types cover all types of data and NUMERIC is a catch-all/default.
However, using a JSON representation of an object, introduces bloat and reduces the usefulness of the converted data from an SQL aspect.
For example say you had :-
#Entity(tableName = "userOtherItemsEntity")
#Parcelize
data class OtherItem (
var objecttype: String?,
#PrimaryKey(autoGenerate = false)
var objectid: Int?,
var subtype: String?,
var collid: Int?,
var name: OtherName?) : Parcelable
#Parcelize
data class OtherName(
var primary: Boolean? = true,
var sortindex: Int? = null,
var content: String? = null) : Parcelable
Then the underlying table does have the name column. The CREATE SQL, generated by Room, would be :-
CREATE TABLE IF NOT EXISTS `userOtherItemsEntity` (`objecttype` TEXT, `objectid` INTEGER, `subtype` TEXT, `collid` INTEGER, `name` TEXT, PRIMARY KEY(`objectid`))
However, you would need TypeConverters which could be :-
#TypeConverter
fun fromOtherName(othername: OtherName ): String {
return Gson().toJson(othername)
}
#TypeConverter
fun toOtherName(json: String): OtherName {
return Gson().fromJson(json,OtherName::class.java)
}
the first using Gson to convert the object to a JSON string, e.g. when inserting data
the second converts the JSON string to an OtherName object.
using Item with Name embedded then data would be stored along the lines of :-
Whilst with the OtherItem with OtherName being converted then the data (similar data) would be along the lines of :-
in the former the 3 Name columns would take up about (1 + 1 + 12) = 16 bytes.
in the latter, The OtherName columns (discounting the word Other whenever used) would take uo some 55 bytes.
the latter may require more complex and resource expensive searches if the components of the OtherName are to be included in searches.
e.g. #Query("SELECT * FROM userItemsEntity WHERE primary") as opposed to #Query("SELECT * FROM userOtherItemsEntity WHERE instr(name,'primary\":true') > 0")

Spring data - RepositoryRestResource PUT returns 200 but does not update object

I'm using Spring's HATEOAS repo:
#RepositoryRestResource(collectionResourceRel = "equipment", path = "equipment")
interface EquipmentRepository : PagingAndSortingRepository<Equipment, Int>
Here's my domain object:
#Entity
data class Equipment(
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
val id: Int?,
val serialNumber: String?,
val modelNumber: String,
val make: String,
val model: String,
val year: Int,
val purchaseDate: Date,
val warrantyEnds: Date,
val cost: Double
)
I'm using cURL and Postman to create equipment and it's all good. If I use PATCH for a partial update the query updates the object - if I use the same URL and same data for PUT nothing is updated but I get a 200 response (and the old object) in postman.
How come I can PATCH http://localhost:8080/equipment/1 works but a PUT with the same data and same endpoint returns 200 but does not update the data?
No errors on the server-side on the console/logs, or any indication as to why I'm getting a 200 and no update...
I am using Kotlin, if it matters...
Try change val to var, i had the same problem and this should help.

get a list of parsed json elements

I parsed a json string to the following object structure using gson:
data class Base (
val expand: String,
val startAt: Long,
val maxResults: Long,
val total: Long,
val issues: List<Issue>
)
data class Issue (
val expand: String,
val id: String,
val self: String,
val key: String,
val fields: Fields
)
data class Fields (
val summary: String,
val issuetype: Issuetype,
val customfield10006: Long? = null,
val created: String,
val customfield11201: String? = null,
val status: Status,
val customfield10002: Customfield10002? = null,
val customfield10003: String? = null
)
Everything works fine and also the object model is correct, because I can access each element of the object.
However, I encountered the problem that I dont know how to get a list of all field-elements. Right now, I have only figured out how to access one item (by using an index and get()-function):
val baseObject = gson.fromJson(response, Base::class.java)
val fieldsList = baseObject.issues.get(0).fields
I actually want to have a list of all field elements and not just one. Is there a gson function allowing me to do that? I couldn't find anything about it in the gson documentation for java.
You don't have to look for some gson function when you've already created a baseObject. You just need to get from each issue it's fields and you can use a map function to achieve this, it will convert each issue to a new type so you can get issue fields there
val fieldFromAllIssues: List<Fields> = baseObject.issues.map { it.fields }
it in this context is a one issue. More explanation about it is here