Convert ByteArrayOutputStream to json in Kotlin - json

I'm trying to create a resource for 2 services, 1 in application/x-www-form-urlencoded and string payload and the other application/json format with json body.
I have this code:
#POST #Path("/test")
fun test(#Context request: ContainerRequest): Response {
val baos = ByteArrayOutputStream()
request.entityStream.use { it.copyTo(baos) }
val ipnRawData = baos.toString()
var map : Map<String,Any>
map = when (request.headers.getFirst("Content-Type")) {
"application/json" -> objectMapper.convertValue(ipnRawData,Map::class.java) as Map<String,Any>
"application/x-www-form-urlencoded" -> LinkedHashMap()
else -> throw UnsupportedOperationException()
}
//....handle the map
return Response.status(200).build()
}
But when I try to run it with the json option, and body: {"name" :"test"}), I get an error:
"java.lang.IllegalArgumentException: Can not construct instance of java.util.LinkedHashMap: no String-argument constructor/factory method to deserialize from String value ('{
"name" :"test"}')"
Thanks for any help, Yoel

You should use mapper.readValue to deserialize JSON into an object.
Using raw Jackson without the Jackson-Kotlin module:
val map: Map<String, String> = JSON.readValue("""{"name" :"test"}""",
object : TypeReference<Map<String, String>>() {})
This passes in an object expression with superclass TypeReference specifying the the type you are wanting to create with full generics still intact (you method suffers from type erasure).
Instead, if you are using the Jackson-Kotlin module you only need:
val map: Map<String, String> = JSON.readValue("""{"name" :"test"}""")
Since it has helper/extension functions to hide some of the uglier things like TypeReference creation.
You should always use the Jackson-Kotlin module with Kotlin code so that you can instantiate any type of Kotlin object including data classes that have all val parameters and no default constructors, have it understand nullability, and also deal with default values for constructor parameters. A simple stand-alone example:
import com.fasterxml.jackson.module.kotlin.*
val JSON = jacksonObjectMapper() // creates ObjectMapper() and adds Kotlin module in one step
val map: Map<String, String> = JSON.readValue("""{"name" :"test"}""")
Notice the import .* so that it picks up all the extension functions otherwise you need to explicitly import: com.fasterxml.jackson.module.kotlin.readValue
Or in your case the modified code would be:
import com.fasterxml.jackson.module.kotlin.readValue
val objectMapper = jacksonObjectMappe() // instead of ObjectMapper()
...
#POST #Path("/test")
fun test(#Context request: ContainerRequest): Response {
val bodyAsString = request.entityStream.bufferedReader().readText()
val map: Map<String, Any> = when (request.headers.getFirst("Content-Type")) {
"application/json" -> objectMapper.readValue(bodyAsString)
"application/x-www-form-urlencoded" -> LinkedHashMap()
else -> throw UnsupportedOperationException()
}
//....handle the map
return Response.status(200).build()
}
The code has also been cleaned up a little to remove the use of a var and to read the entity stream in a more Kotlin friendly way.
Also note that the Content-Type header may be more complicated, it could contain encoding as well such as:
Content-type: application/json; charset=utf-8
So you may want a utility function that checks if the header is "equal to application/json or starts with application/json;" instead of only an equality check.
Lastly you could pass the request.entityStream directly to objectMapper.readValue and never copy it into a string at all. There are various overloads for readValue that are helpful for these types of inputs.

Related

Kotlin reading json unknown type Spring

I'm calling different APIs, that use the same key name in the JSON file. Depending on the response, there's one field that may be different types.
To be clear:
The key "results" when calling the API nº1 is a JSON object
The key "results" when calling the API nº2 is a JSON array
My code looks like this when using the second API:
data class Result(
#SerializedName("results") var persons:ArrayList<Person> =ArrayList()
)
The question is if there's any way to use the same class, without taking care if it's a JSON array or a JSON object.
I believe you can define results as an instance of com.fasterxml.jackson.databind.JsonNode.
data class Result(
val results: JsonNode
)
Then you can process results based on it's type—whether it is an ArrayNode or an ObjectNode (as both extend JsonNode):
fun processResults(results: JsonNode) = when{
results.isArray -> processArrayNode(results)
else -> processObjectNode(results)
}
private fun processArrayNode(list: JsonNode): *return whatever you need*{
val elements = list
.elements()
.asSequence()
.toList()
val mappedElements = elements.map{
processObjectNode(it)
}
// do whatever you need with the array
}
private fun processObjectNode(person: JsonNode): *return whatever you need*{
//** this will transform the json node into a linkedHashMap where the keys are the json keys and the values are the values (here interpreted as jsonNodes) **/
val fieldsMap = person
.fields()
.asSequence()
.associateBy( {it.key}, {it.value} )
// process whatever you need
}
This is one way to use the same DTO for both API calls. In my opinion, it is not worth the extra work. I would create two DTOs containing the results field, where in one it is an instance of Person, and in the other it is an instance of List<Person>.
Edit: One little upgrade to the above snippet would be to add extension methods to JsonNode:
fun JsonNode.elementsToList(): List<JsonNode> = this
.elements()
.asSequence()
.toList()
fun JsonNode.fieldsToMap(): Map<String, JsonNode> = this
.fields()
.asSequence()
.associateBy({it.key}, {it.value})
You can use ObjectMapper.typeFactory.constructParametricType to handle generic types:
data class Result<T>(
var x:T
)
val om = ObjectMapper()
om.registerModule(KotlinModule())
val parsedList = om.readValue<Result<List<String>>>(
"""{"x":["x1", "x2"]}""",
om.typeFactory.constructParametricType(Result::class.java, List::class.java)
)
println(parsedList)
val parsedMap = om.readValue<Result<Map<String, String>>>(
"""{"x":{"k1": "v1", "k2": "v2"}}""",
om.typeFactory.constructParametricType(Result::class.java, Map::class.java)
)
println(parsedMap)
Gives output:
Result(x=[x1, x2])
Result(x={k1=v1, k2=v2})

Kotlin Serialization - Decoding JSON Array from string

Trying to deserialize cached json string to data object and getting exception: kotlinx.serialization.json.internal.JsonDecodingException: Expected class kotlinx.serialization.json.JsonObject (Kotlin reflection is not available) as the serialized body of kotlinx.serialization.Polymorphic<List>, but had class kotlinx.serialization.json.JsonArray (Kotlin reflection is not available)
Code used to deserialize
internal inline fun <reified R : Any> String.convertToDataClass() =
Json {
ignoreUnknownKeys = true
}.decodeFromString(R::class.serializer(), this)
Code example:
val jsonString ="""
[{"name1":"value1"}, {"name2":"value2"}]
"""
val dataObject = jsonString.convertToDataClass<List<SomeObject>>()
When going through Ktor pipeline everything works fine but it is breaking on attempt to deserialize the same response body cached as string.
I am aware of that R::class.serializer() is marked as for internal usage but this is the only way known to me how to deserialize generics from string content.
There is a fitting extension function available at kotlinx.serialization.decodeFromString that takes one generic parameter, so you could pass R as generic to that extension.
Check https://github.com/Kotlin/kotlinx.serialization#introduction-and-references. The sample is val obj = Json.decodeFromString<Project>(string), which will fit your needs doing something like this
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
internal inline fun <reified R : Any> String.convertToDataClass() =
Json {
ignoreUnknownKeys = true
}.decodeFromString<R>(this)

Using a KClass reference as a reified parameter to deserialize from JSON

I'm trying to implement a general serialization framework to convert outgoing and incoming messages to json using the kotlinx serialialization. I'm developing a multiplatform app, so I'm trying to get it to run on KotlinJVM and KotlinJS.
For this, I add a type field to every message and use a map that maps each type string to a KClass. What's the type for that map? It contains KClass<> objects whose classes extend the Message class, therefore in java I'd specify my map as
Map<KClass<? extends Message>, String>.
How can I do that in Kotlin?
Afterwards I need to serialize and deserialize the message based on its key and therefore type. Java frameworks take a Class parameter for the type of the object I want to deserialize/instantiate (e.g. gson.fromJson(ClientMessage.class)). In Kotlin this is done using reified parameters Json.decodeFromString<Type>. I do not know the type of the message at compile time though and just have a reference to a KClass, how can I instantiate an object based on that?
#Serializable
open class Message(val type: String) {
companion object {
val messageTypes: Map<KClass<out Message>, String> = mapOf(
ClientLoginMessage::class to "clientLoginMessage",
Message::class to "message"
)
inline fun <reified T> getMessageTypeByClass(): String = messageTypes[T::class]!! // utility for defining the type in the constructors of the individual messages
}
fun toJson() = Json.encodeToString(this)
fun fromJson(json: String): Message? {
val plainMessage = Json.decodeFromString<Message>(json) // get type string from json
return messageTypes.entries.find { it.value == plainMessage.type }?.let {
// how can I use the KClass from it.key as reified parameter?
Json.decodeFromString<?????>(json)
}
}
}
#Serializable
class ClientLoginMessage
: Message(Message.getMessageTypeByClass<ClientLoginMessage>()) {}
Create a map of serializers like for types:
val serializers: Map<KClass<out Message>, KSerializer<out Message>> = mapOf(
ClientLoginMessage::class to ClientLoginMessage.serializer(),
Message::class to Message.serializer()
)
Pass in the serializer needed to Json.decodeFromString like this:
fun fromJson(json: String): Message? {
val plainMessage = Json.decodeFromString<Message>(json) // get type string from json
return messageTypes.entries.find { it.value == plainMessage.type }?.let {
// how can I use the KClass from it.key as reified parameter?
Json.decodeFromString(serializers.get(plainMessage.type)!!, json)
}
}
You might also want to have a look at the Kotlin built in handling of polymorphic classes: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md

How can I programmatically create a validation contract at compile-time?

I apologize in advance if this is an XY problem.
tl;dr:
I'd like to have a compile-time map of type [Request.type, Response.type] so I can effectively say if I send message Request, a CLI should, at compile-time, know how to deserialize its expected Response, irrespective of the fact that it won't know what type of request is sent until runtime.
too long; still read:
I have a CLI which communicates with an HTTP server and depending on the type of message sent to the HTTP server, I'd like to validate the JSON response against a case case.
For instance, if I send the HTTP server an AddFoo message, I might want to validate that the JSON response can be deserialized into an AddedFoo, etc.
My current solution is quite hacky. Using play-json, I'm attempting to parse the JSON response using a mapping from config.mode (i.e., command issued to the CLI) to the expected responses' implicit Reads.
My code looks something like this:
val modeToResponseReads: Map[String, Reads[_]] = Map(
Modes.ADD_FOO -> AddedFoo.addedFooReads,
Modes.ADD_BOO -> AddedBoo.addedBooReads,
Modes.GET_WOO -> GetWooResponse.getWooReads,
)
parser.parse(args, MyConfig()) match {
case Some(config) => try {
val exec = new MyHttpExecutor(remoteUri, config)
val res = Await.result(exec.getResponse, 100.seconds)
// passing `Reads` to `as` because JsValue#as[T] cannot be
// applied at runtime -- only compile-time.
val _ = Json.parse(res.json.toString)
.as(modeToResponseReads(config.mode))
exec.actorSystem.terminate()
exec.wsClient.close()
} catch {
case t: Throwable => logger.error(t.getMessage)
}
case None => {
logger.error("Bad arguments.")
sys.exit(1)
}
}
While this works, it's an incredible kludge that becomes increasingly unmaintainable with an increasing number of messages. Further, I've found that this pattern will need to be replicated anywhere some type of validate or conversion will need to happen (e.g., Future[Any] being converted to Future[AddedFoo]).
Surely my approach isn't the right way... how is this traditionally done? If it is the right way (please no), are there optimizations that can be made?
I managed to accomplish this by encoding the contract directly into the child Request classes. Namely, the child Request classes would hold a ResponseType type with the base class enforcing the covariant type.
So I can do something like this:
abstract class Response
abstract class Request[+A <: Response]
case class Foo(id: String)
object Foo {
implicit val fooReads = Json.reads[Foo]
implicit val fooFormat = Json.format[Foo]
}
case class FooResponse(foo: Foo) extends Response {
def greet = println("woo hoo!")
}
object FooResponse {
implicit val fooRespReads = Json.reads[FooResponse]
implicit val fooRespFormat = Json.format[FooResponse]
}
case class FooRequest() extends Request[FooResponse] {
type ResponseType = FooResponse
}
object Main extends App {
val req: FooRequest = new FooRequest()
val foo = Foo("12345")
val resp = new FooResponse(foo)
val respJsonString = Json.toJson(resp).toString
println(Json.parse(respJsonString).as[req.ResponseType])
}

How do I convert a Scala List[org.bson.Document] to a JSON String?

I had a function in AWS Lambda:
def test(pj: Pojo, context: Context): java.util.List[Document]
that was not initializing the pj with the input JSON values at all.
I found another way of doing AWS Lambda in Scala like this:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
val scalaMapper = new ObjectMapper().registerModule(new DefaultScalaModule)
def test(input: InputStream, output: OutputStream): Unit = {
val inputPojo = scalaMapper.readValue(input, classOf[Pojo])
val answer: Seq[Document] = getTheRealAnswer(inputPojo)
val jsonStr = "{ frustration: \"I wish my answer was JSON.\" }"
output.write(jsonStr.getBytes("UTF-8"))
}
and that works, except what I really want to return as an answer is a JSON array of Documents. How should I go about that?
Edit: In my original posting, I wrote: "[the first example] was returning the answer as an error 22. Basically AWS (I think) treated the JSON conversion of the List[Document] as a filename, JSON has plenty of colons, and the error 22 came from colons in filenames not being allowed. Weird." That turned out to be an error in my invocation of the AWS Lambda Function from AWS CLI. I omitted the output filename in the command invocation, and returned JSON was interpreted by AWS CLI as a filename.
Since I wrote this message, I got things to work like this:
def jsonizeDocs(cDocument: Seq[Document]): String = {
val sb=new StringBuilder
for (doc <- cDocument) {
if (sb.nonEmpty) {
sb.append(",")
}
sb.append(doc.toJson)
}
sb.toString
}
Note! This answer is based on a light wrapper I wrote around json4s which I call JSON Extensions
Assuming you are using Scala Objects, import the io.onema.json.Extensions._
import io.onema.json.Extensions._
case class Doc(title: String, content: String)
val listOfDocs = Seq(Doc("Foo", "bar"), Doc("Bar", "Baz"), Doc("Blah", "Bax"))
val json: String = listOfDocs.asJson
println(json)
// [{"title":"Foo","content":"bar"},{"title":"Bar","content":"Baz"},{"title":"Blah","content":"Bax"}]
See the running example here
Now, since you are using a Pojo, you need to import io.onema.json.JavaExtensions._. Assuming you have the following POJO:
public class Document {
private String title;
private String content;
public String getTitle() {return title;}
public String getContent() {return content;}
public void setTitle(String title) { this.title = title;}
public void setContent(String content) {this.content = content;}
}
Use this method in your Scala code like such:
import io.onema.json.JavaExtensions._
import com.example.Document
// ...
def jsonizeDocs(cDocument: Seq[Document]): String = {
val json: String = cDocument.asJson
println(json)
json
}
In AWS Lambda (and to go the other way around) use jsonDecode and a custom object mapper to deserialize to the expected type:
import io.onema.json.JavaExtensions._
import io.onema.json.Mapper
import com.example.Document
val jsonString = """[{"title":"Foo","content":"bar"},{"title":"Bar","content":"Baz"},{"title":"Blah","content":"Bax"}]"""
val mapper: ObjectMapper = Mapper.allowUnknownPropertiesMapper
val doc: Document = jsonString.jsonDecode[Document](mapper)
I have used the method described here quite successfully in a lambda framework that is able to deserialize to AWS lambda events as well as custom types, see a simple example here.
That's it! you can use this library or one of the many JSON serializers in Java or Scala. If you know the type of your objects most libraries will enable you to serialize to JSON and back very easily.