Deserialize JsonArray with kotlinx serialization - kotlin

I have the following Json object:
{
"List":[
"string1",
"string2",
"string3",
"..."
]
}
This deserializes into a JsonArray containing JsonLiterals.
When deserializing this and trying to iterate over it I get an error:
java.lang.ClassCastException: kotlinx.serialization.json.JsonLiteral cannot be cast to java.lang.String
What would be the standard way to achieve something like that?

First, make sure that you are using the latest version of kotlinx.serialization.
A comment in the implementation of JsonLiteral states that
JsonLiteral is deprecated for public use and no longer available. Please use JsonPrimitive instead
JsonPrimitive has the isString method to check if it is a string and you can then access it using its content method.

Related

Need to serialize Any to base64 string in Kotlin

I have the following code in Kotlin which I aim to use it to convert any instance to a base64 encoded string. The same is not working and it throws the following error :
Serializer for class 'Any' is not found.\nMark the class as #Serializable or provide the serializer explicitly
How can I fix this?
class SerializerAdapter: SerializerPort {
private val logger: Logger = LoggerFactory.getLogger(javaClass.simpleName)
override fun toBase64(input: Any): String {
try {
val jsonString = Json.encodeToString(input)
return Base64.getEncoder().encodeToString(jsonString.toByteArray())
}catch (ex: Exception) {
logger.error("[BASE64 Error] error converting json object to base64 encoded string: ${ex.stackTraceToString()}")
}finally {
return ""
}
}
}
Serializing just Any is not as simple as it sounds. Serialization framework has to know the type of the data to serialize. It can use either compile type (Any in your case) or runtime type (actual type provided to toBase64()). Both options have their drawbacks. Runtime type is incomplete due to type erasure, so e.g. List<Int> and List<String> are the same. On the other hand, compile-time type may be totally lost, e.g. in generics or in cases like yours.
Kotlin serialization generally prefers compile types, especially because reified parameters make them much more usable. Unfortunately, we can't use reified here, because toBase64() is a virtual function, so it can't be inlined.
My suggestion is to change the signature of this function to additionally receive KType of provided data and then create inline function to make it convenient to use:
override fun toBase64(input: Any, type: KType): String {
try {
val serializer = Json.serializersModule.serializer(type)
val jsonString = Json.encodeToString(serializer, input)
...
}
}
#OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> SerializerPort.toBase64(input: Any) = toBase64(input, typeOf<T>())
Alternatively, we can serialize using the runtime type, but note the problems I mentioned earlier - it may not work well with generics.
val serializer = Json.serializersModule.serializer(input::class.starProjectedType)
val jsonString = Json.encodeToString(serializer, input)
kotlinx.serialization is a compile-time library, so you must know up front every object you want to serialise.
Depending on your use case;
You would either need to use a runtime library that uses reflection (such as one of the many JSON, or XML serialisers) to be able to use classes that are not your own.
Or if your requirement is more that you want to be able to serialise other peoples classes then you can use your own interface that others must implement along with #Serializable (see https://github.com/Kotlin/kotlinx.serialization/issues/1005).

Quarkus/Kotlin: An annotation argument must be a compile-time constant with a java class

In a quarkus/kotlin app, I have a rest client that is very basic:
#Path("/my/api/v1")
#RestClient
interface MyApiClient { }
Problem is, when a request fails, it returns a response that fails to be mapped. So I want to add an exception mapper, in order to log the real error:
class MyExceptionMapper : ResponseExceptionMapper<java.lang.RuntimeException?> {
override fun toThrowable(r: Response): java.lang.RuntimeException {
Logger.getLogger(MyApiClient::class.java).error(r.status)
return RuntimeException("failed")
}
}
To do so, I should annoate my client with:
#RegisterProvider(MyExceptionMapper::class.java)
Doing so, I have a kotlin error:
An annotation argument must be a compile-time constant
I googled but could find solutions only for strings. In this case, kotlin expects the java class to be a compile time constant. How to get it?
Should work with simple:
#RegisterProvider(MyExceptionMapper::class)

Pass annotation to a function in Kotlin

How can I pass an annotion instance to a function?
I would like to call the java method AbstractCDI.select(Class<T> type, Annotation... qualifiers). But I don't know how to pass an annotation instance to this method.
Calling the constructor like
cdiInstance.select(MyClass::javaClass, MyAnnotation())
is not allowed and the #Annotation-Syntax cdiInstance.select(MyClass::javaClass, #MyAnnotation) is not allowed as parameter, too. How can I archive this?
When working with CDI you usually also have AnnotationLiteral available or at least you can implement something similar rather easy.
If you want to select a class using your annotation the following should do the trick:
cdiInstance.select(MyClass::class.java, object : AnnotationLiteral<MyAnnotation>() {})
Or you may need to implement your specific AnnotationLiteral-class if you require a specific value. In Java that would work as follows:
class MyAnnotationLiteral extends AnnotationLiteral<MyAnnotation> implements MyAnnotation {
private String value;
public MyAnnotationLiteral(String value) {
this.value = value;
}
#Override
public String[] value() {
return new String[] { value };
}
}
In Kotlin however, you can't implement the annotation and extend AnnotationLiteral or maybe I just did not see how (see also related question: Implement (/inherit/~extend) annotation in Kotlin).
If you rather want to continue using reflection to access the annotation then you should probably rather use the Kotlin reflection way instead:
ClassWithAnno::class.annotations
ClassWithAnno::methodWithAnno.annotations
Calling filter, etc. to get the Annotation you desire or if you know there is only one Annotation there, you can also just call the following (findAnnotation is an extension function on KAnnotatedElement):
ClassWithAnno::class.findAnnotation<MyAnnotation>()
ClassWithAnno::methodWithAnno.findAnnotation<MyAnnotation>()
One could annotate a method or field with the annotation an get it per Reflection:
this.javaClass.getMethod("annotatedMethod").getAnnotation(MyAnnotation::class.java)
Or According to Roland's suggestion the kotlin version of the above:
MyClass::annotatedMethod.findAnnotation<MyAnnotation>()!!
As suggested by Roland for CDI it is better to use AnnotationLiteral (see his post).

Deserialize option<'a>

When trying to deserialize a record member of type Header option returned from a JSON string, I get the following exception:
The data contract type
'Microsoft.FSharp.Core.FSharpOption`1[[MyWeb.Controllers.Header,
MyWeb.Controllers, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null]]' cannot be deserialized because the required
data member 'value' was not found.
I'm serializing/deserializing a Message record:
[<DataContract>]
type Header =
{ [<DataMember>] mutable ID : int
[<DataMember>] mutable Description : string }
[<DataContract>]
type Message =
{ [<DataMember>] mutable ID : int
[<DataMember>] mutable Header : Header option
[<DataMember>] mutable SenderID : string
[<DataMember>] mutable ReceiverID : string }
The code I use to deserialize the JSON:
let deserializeJson<'a> (s:string) =
use ms = new MemoryStream(ASCIIEncoding.ASCII.GetBytes s)
let serialize = DataContractJsonSerializer(typeof<'a>)
serialize.ReadObject ms :?> 'a
And the actual raw JSON result:
"Message":
{
"ID":13,
"Header": { "Value":{"ID":21,"Description":"some"}},
"SenderID":"312345332423",
"ReceiverID":"16564543423"
}
The question: how do I deserialize a 'a option?
Update
ASP.NET MVC uses JavaScriptSerializer by default to serialize objects and I'm using DataContractJsonSerializer to deserialize.
For some reason it seems DataContractJsonSerializer can't read the JSON string unless the Value property for the option is in lowercase (as pointed out by #svick). A dirty fix would be to replace "Value" with "value" in the returned JSON string, but I've chosen to go with Roberts' suggestion.
If you were to hop over to using json.net (a.k.a Newtonsoft.Json) instead of the json serializer that comes with the .NET framework, then you could use the option serializer I built to allow me to work more effectively with ravendb. Should just be a matter of registering the convert with the serializer and calling Deserialize.

Using asType with Mixin annotation

I'd like write a custom type conversion Category in Groovy. The goal is to assign the values of a Map to the fields of a Groovy bean. In the future there will be different response types. The values of of the Map are always of type String but will have to be converted into a different data type. To make this work I created a Category class that implements a method named asType. This is a simplified example of my code:
class MapCategory {
static Object asType(Map self, Class clazz) {
if(clazz == Response) {
Response response = new Response()
self.each { key, value ->
response.setProperty(key, value)
}
return response
}
DefaultGroovyMethods.asType(self, clazz)
}
}
class Response {
String result
String message
}
This works just fine when when I apply the category using the use keyword.
use(MapCategory) {
println [result: 'OK', message: 'Success'] as Response
}
However, when I try to use the #Mixin annotation instead it doesn't seem to work correctly. I get the correct response type but all fields are null.
#Mixin(MapCategory)
class MyClass {
def printResponse() {
println [result: 'OK', message: 'Success'] as Response
}
}
Does anybody know why it doesn't work correctly using the annotation?
Mixins don't work that way. You are trying to mix in the method for Map into your MyClass object. The mixin would only work if MyClass extended Map.
Instead, you want to use the use keyword like normal, and just use your category as a category.
Alternatively, you might not need it at all. Did you know that you can, by default, convert any map into any GroovyBean without extra code? Just use the map-based constructor, like so:
#groovy.transform.Canonical // Groovy 1.8, just added for automatic toString method
class Response {
String result
String message
}
println new Response([result: 'OK', message: 'Success'])
println([result: 'bad', message: 'blah'] as Response)
Notice, automatic Map conversion works both ways. It's a built-in feature of Groovy.
Of course, if you need something more complex than just assigning bean properties, this won't help.
Note: I'd give you a link, but the Groovy website appears to be broken, and I can't find code examples. :-(
EDIT: Another Suggestion
Instead of using a Category at all, why don't you let the bean itself handle it:
#groovy.transform.Canonical
class Response {
String result
String message
int num
public void setNum(String num) {
this.num = Integer.parseInt(num)
}
}
def map = [result: 'OK', message: 'Success', num: '35' ]
println map as Response