How to leave json object as raw string in Moshi - kotlin

I want be able to save json object as raw json String. I've already have solution wit JSONObject, but it seems a bit redundant. Is there any way to get json string from reader?
class ObjectAsStringFactory : JsonAdapter.Factory {
override fun create(
type: Type,
annotations: MutableSet<out Annotation>,
moshi: Moshi
): JsonAdapter<*>? {
val delegateAnnotations = Types.nextAnnotations(annotations, ObjectAsString::class.java)
?: return null
if (Types.getRawType(type) !== String::class.java) {
throw IllegalArgumentException("#ObjectAsString requires the type to be String. Found this type: $type")
}
val delegateAdapter: JsonAdapter<String?> = moshi.adapter(type, delegateAnnotations)
return ObjectAsStringAdapter(delegateAdapter)
}
class ObjectAsStringAdapter(private val wrapped: JsonAdapter<String?>) : JsonAdapter<String>() {
override fun fromJson(reader: JsonReader): String? {
return (reader.readJsonValue() as? Map<*, *>)?.let { data ->
try {
JSONObject(data).toString()
} catch (e: JSONException) {
return null
}
}
}
override fun toJson(writer: JsonWriter, value: String?) {
wrapped.toJson(writer, value)
}
}
}

Try JsonReader.nextSource(). You can read the encoded JSON bytes either as a String or as a ByteString.
String encodedJson;
try (BufferedSource bufferedSource = reader.nextSource()) {
encodedJson = bufferedSource.readUtf8();
}

Related

How to get data with passing parameter in android retrofit kotlin

I want to pass String parameter to the interface and get String data processed with String I passed.
But I can't response.isSuccessful == true.
"/danmun/m" is get one String parameter and return String data.
How can I get the processed String data which I passed.
interface Danmun_processing {
#GET("http://118.67.123.8/danmun/m")
fun getDataParam(#Query("param1") param1 : String): Call<String>
}
val danmunProcessing = retrofit.create(Danmun_processing::class.java)
val sentence = "sample string data"
danmunProcessing.getDataParam(sentence).enqueue(object : Callback<String> {
override fun onResponse(
call: Call<String>,
response: Response<String>
) {
if(response.isSuccessful) {
}
else {
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
}
})
I want to get the processed String data with using data I passed.

Spring webtestclient serializes dates to timestamps instead of dates

I am trying to check if the data I get back from the webtestclient is the same as what I expect. But the ZonedDateTime from the User data class is not just shown as a date but as a timestamp while I have applied Jackson to the webtestclient codecs. Example: 2021-12-09T16:39:43.225207700+01:00 is converted to 1639064383.225207700 while I expect nothing to change. Could someone maybe explain what I am doing wrong. (Using this jackson config when calling this endpoint outside of the test gives the date not as timestamp)
WebTestClientUtil:
object WebTestClientUtil {
fun webTestClient(routerFunction: RouterFunction<ServerResponse>): WebTestClient {
return WebTestClient
.bindToRouterFunction(routerFunction)
.configureClient()
.codecs { configurer: ClientCodecConfigurer ->
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON))
}
.build()
}
}
Testcase:
#Test
fun `get user when given correct data`() {
val user = GlobalMocks.mockedUser
coEvery { userRepository.getUserWithData(any()) } returns user
val result = webTestClient.get()
.uri("/api/v1/user/${user.userId}")
.exchange()
.expectStatus().is2xxSuccessful
.expectBody<Result>().returnResult().responseBody?.payload
assertEquals(user, result)
}
data class Result(
val payload: User
)
Jackson config:
class JacksonConfig {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
val objectMapper = jacksonObjectMapper().applyDefaultSettings()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}
EDIT: Also found this issue which makes me think that it might have something to do with bindToRouterFunction.
You need to define an ObjectMapper bean so that the auto-configured one is not used:
#Configuration(proxyBeanMethods = false)
class JacksonConfiguration {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
}
#Bean
fun objectMapper() = jacksonObjectMapper().applyDefaultSettings ()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}

Custom multipart-form-data serializable in Kotlin/Ktor

Does anyone know how to program the override function convertForReceive of a custom Multipart.FormData converter?
I want to convert the multipart request to my class with the converter but I don't know how it works.
I have:
Application.kt
install(ContentNegotiation) {
json()
register(ContentType.MultiPart.FormData, CustomMultipartConverter)
}
CustomMultipartConverter
object CustomMultipartConverter: ContentConverter {
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
TODO("Not yet implemented")
}
override suspend fun convertForSend(
context: PipelineContext<Any, ApplicationCall>,
contentType: ContentType,
value: Any
): Any? {
TODO("Not yet implemented")
}
}
REQUEST CLASS
class CreatePostRequest(
val text: String,
val image: File? = null
)
ROUTE
route("v1/posts") {
authenticate {
route("create") {
val authJWT = call.authentication.principal as JWTAtuh
val request = call.receive<CreatePostRequest>()
//myCode
call.respond(HttpStatusCode.OK)
}
}
}
You can take SerializationConverter as a reference:
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
val request = context.subject
val channel = request.value as? ByteReadChannel ?: return null
val charset = context.call.request.contentCharset() ?: defaultCharset
val serializer = format.serializersModule.serializer(request.typeInfo)
val contentPacket = channel.readRemaining()
return when (format) {
is StringFormat -> format.decodeFromString(serializer, contentPacket.readText(charset))
is BinaryFormat -> format.decodeFromByteArray(serializer, contentPacket.readBytes())
else -> {
contentPacket.discard()
error("Unsupported format $format")
}
}
}

Get value from annotation failed

This is annotation definition:
#Target(AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
#MustBeDocumented
annotation class MyAnno(val desc: String, val comment: String) { }
And below is where the MyAnno used:
class MyAnnoUser {
#MyAnno(desc = "name", comment = "name comment")
lateinit var name: String
#MyAnno(desc = "age", comment = "age comment")
var age: Int = 0
#MyAnno(desc = "money", comment = "money comment")
var money: Double = 0.0
#MyAnno(desc = "gender", comment = "gender comment")
var gender: Boolean = false
override fun toString(): String {
return "(name: $name; age: $age; money: $money; gender: ${if (gender) "men" else "women"})"
}
}
Here's code to read the value in MyAnno:
class MyAnnoExpression(val obj: Any, val context: Context) {
val numTypeSet = setOf("Int", "Double", "Byte")
fun expression() {
val clazz = obj::class
clazz.declaredMemberProperties.forEach { prop ->
val mutableProp = try {
prop as KMutableProperty<*>
} catch (e: Exception) {
null
} ?: return#forEach
val desc = mutableProp.findAnnotation<MyAnno>()
desc?.let {
val propClassName = mutableProp.returnType.toString().removePrefix("kotlin.")
when (propClassName) {
in numTypeSet -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toNum(propClassName))
"String" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String))
"Boolean" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toBoolean())
}
}
}
}
private fun readProp(value: MyAnno, context: Context): Any? {
val prop = Properties()
val input = context.assets.open("app.properties")
prop.load(InputStreamReader(input, "utf-8"))
return prop.get(value.desc)
}
}
Now the Debugger shows me following info of value in readProp(...) function:
#com.demo.basekotlin.MyAnno(comment=age comment, desc=age)
But i got exception when read desc from value:
An exception occurs during Evaluate Expression Action : org.jetbrains.eval4j.VOID_VALUE cannot be cast to org.jetbrains.eval4j.AbstractValue
I can't find any thing wrong in my code, is there another program setting needed?
As I understand you just want to see annotation value for given property.
First, let's declare an annotation.
#Target(PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class PropertyAnnotation(val desc: String)
Container:
class Container {
#PropertyAnnotation("Name")
var name: String? = null
#PropertyAnnotation("Age")
var age: Int = -1
var notAnnotatedProperty: String = "not annotated"
}
And finally, code responsible for get all declared properties, then find a properties annotated as PropertyAnnotation, cast it to it, and get value from it.
fun main() {
val container = Container()
container::class.declaredMemberProperties.forEach { property ->
(property.annotations.find {
it is PropertyAnnotation
} as? PropertyAnnotation)?.let {
println("Property: `$property` is ${it.desc}")
}
}
}
Output:
Property: `var Container.age: kotlin.Int` is Age
Property: `var Container.name: kotlin.String?` is Name
Kind ugly. But, let's use more Kotlin pro-dev-features.
Let's create extension function for any not-null type which returns all member property of given type:
inline fun <reified T : Any> Any.getMemberProperty(): List<T> {
return this::class.declaredMemberProperties.mapNotNull { prop ->
(prop.annotations.find { ann -> ann is T }) as? T
}
}
And now usage:
fun main() {
val container = Container()
container.getMemberProperty<PropertyAnnotation>().forEach {
println(it.desc)
}
}

Kotlin + Arrow + Gson = None?

I have a model in Kotlin of a simple library of Books and Borrowers where a Book is checked out if it has a Borrower. I use Arrow Option to encode the absence/presence of a Borrower:
data class Borrower(val name: Name, val maxBooks: MaxBooks)
data class Book(val title: String, val author: String, val borrower: Option<Borrower> = None)
I am having trouble serializing/deserializing these objects to/from JSON in Gson - specifically the representation of an Option<Borrower> to a JSON null within a Book:
[
{
"title": "Book100",
"author": "Author100",
"borrower": {
"name": "Borrower100",
"maxBooks": 100
}
},
{
"title": "Book200",
"author": "Author200",
"borrower": null
}
]
My deserialize code:
fun jsonStringToBooks(jsonString: String): List<Book> {
val gson = Gson()
return try {
gson.fromJson(jsonString, object : TypeToken<List<Book>>() {}.type)
} catch (e: Exception) {
emptyList()
}
}
I get an empty list. The nearly identical jsonStringToBorrowers works fine.
Can someone point me in the right direction?
Would using a different JSON library like kotlinx.serialization or Klaxon be a better idea and how do they do the null <-> None thing?
Thank you!
The issue is a bit hidden by the fact that you don't log the exception before returning an empty list. If you logged that exception you would have gotten this:
java.lang.RuntimeException: Failed to invoke private arrow.core.Option() with no args
This means that Gson doesn't know how to create an Option class because it has no public empty constructor. Indeed, Option is a sealed class (hence abstract) having 2 concrete children classes: Some and None. In order to get an instance of Option you should use one of the factory methods, like Option.just(xxx) or Option.empty() among the others.
Now, in order to fix your code you need to tell Gson how to deserialize an Option class. To do that, you need to register a type adapter to your gson object.
A possible implementation is the following:
class OptionTypeAdapter<E>(private val adapter: TypeAdapter<E>) : TypeAdapter<Option<E>>() {
#Throws(IOException::class)
override fun write(out: JsonWriter, value: Option<E>) {
when (value) {
is Some -> adapter.write(out, value.t)
is None -> out.nullValue()
}
}
#Throws(IOException::class)
override fun read(input: JsonReader): Option<E> {
val peek = input.peek()
return if (peek != JsonToken.NULL) {
Option.just(adapter.read(input))
} else {
input.nextNull()
Option.empty()
}
}
companion object {
fun getFactory() = object : TypeAdapterFactory {
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
val rawType = type.rawType as Class<*>
if (rawType != Option::class.java) {
return null
}
val parameterizedType = type.type as ParameterizedType
val actualType = parameterizedType.actualTypeArguments[0]
val adapter = gson.getAdapter(TypeToken.get(actualType))
return OptionTypeAdapter(adapter) as TypeAdapter<T>
}
}
}
}
You can use it in the following way:
fun main(args: Array<String>) {
val gson = GsonBuilder()
.registerTypeAdapterFactory(OptionTypeAdapter.getFactory())
.create()
val result: List<Book> = try {
gson.fromJson(json, TypeToken.getParameterized(List::class.java, Book::class.java).type)
} catch (e: Exception) {
e.printStackTrace()
emptyList()
}
println(result)
}
That code outputs:
[Book(title=Book100, author=Author100, borrower=Some(Borrower(name=Borrower100, maxBooks=100))), Book(title=Book200, author=Author200, borrower=None)]