Kotlinx serialization parsing enum ignore unknown value - json

I've a json which looks like:
[
{
"object": [
{
"enumValue1": "value1",
"value2": 1
}
],
},
{
"object2": [
{
"enumValue1": "value2",
"value2": 2
}
],
},
{
"object3": [
{
"enumValue1": "value1",
"value2": 3
}
],
},
{
"object4": [
{
"enumValue1": "unknown",
"value2": 4
}
],
},
]
I want to parse this json with kotlinx serialization
I've created a class and an enum:
#Serializable
data class Class {
#SerialName("enumValue1")
val enumValue1: EnumValue
#SerialName("value2")
val value2: Int
}
#Serializable
enum class EnumValue {
#SerialName("value1") VALUE_1,
#SerialName("value2") VALUE_2
}
I expect the output of the parsing to be a list, with 3 objects in (object with value "unknown" not parsed)
How could I achieve it?
I have try:
ignoreUnknownKeys = true
coerceInputValues = true
But it doesn' work:
Field 'enumValue1' is required for type with serial name, but it was missing
Thanks for your help

You should declare enumValue1 as nullable:
val enumValue1: EnumValue?
That will make it optional.

Related

Scala Json parsing

This is the input json which I am getting which is nested json structure and I don't want to map directly to class, need custom parsing of some the objects as I have made the case classes
{
"uuid": "b547e13e-b32d-11ec-b909-0242ac120002",
"log": {
"Response": {
"info": {
"receivedTime": "2022-02-09T00:30:00Z",
"isSecure": "Yes",
"Data": [{
"id": "75641",
"type": "vendor",
"sourceId": "3",
"size": 53
}],
"Group": [{
"isActive": "yes",
"metadata": {
"owner": "owner1",
"compressionType": "gz",
"comments": "someComment",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "1"
},
{
"isActive": "yes",
"metadata": {
"owner": "owner12",
"compressionType": "snappy",
"comments": "someComment",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "2"
},
{
"isActive": "yes",
"metadata": {
"owner": "owner123",
"compressionType": "snappy",
"comments": "someComment",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "4"
},
{
"isActive": "yes",
"metadata": {
"owner": "owner124",
"compressionType": "snappy",
"comments": "someComments",
"createdDate": "2022-01-11T11:00:00Z",
"updatedDate": "2022-01-12T14:17:55Z"
},
"setId": "4"
}
]
}
}
}
}
Code that I am trying play json also tried circe . Please help ..New to scala world
below is object and case class
case class DataCatalog(uuid: String, data: Seq[Data], metadata: Seq[Metadata])
object DataCatalog {
case class Data(id: String,
type: String,
sourceId: Option[Int],
size: Int)
case class Metadata(isActive: String,
owner: String,
compressionType: String,
comments: String,
createdDate: String,
updatedDate: String
)
def convertJson(inputjsonLine: String): Option[DataCatalog] = {
val result = Try {
//val doc: Json = parse(line).getOrElse(Json.Null)
//val cursor: HCursor = doc.hcursor
//val uuid: Decoder.Result[String] = cursor.downField("uuid").as[String]
val lat = (inputjsonLine \ "uuid").get
DataCatalog(uuid, data, group)
}
//do pattern matching
result match {
case Success(dataCatalog) => Some(dataCatalog)
case Failure(exception) =>
}
}
}
Any parsing api is fine.
If you use Scala Play, for each case class you should have an companion object which will help you a lot with read/write object in/from json:
object Data {
import play.api.libs.json._
implicit val read = Json.reads[Data ]
implicit val write = Json.writes[Data ]
def tupled = (Data.apply _).tupled
}
object Metadata {
import play.api.libs.json._
implicit val read = Json.reads[Metadata ]
implicit val write = Json.writes[Metadata ]
def tupled = (Metadata.apply _).tupled
}
Is required as each companion object to be in same file as the case class. For your json example, you need more case classes because you have a lot of nested objects there (log, Response, info, each of it)
or, you can read the field which you're interested in as:
(jsonData \ "fieldName").as[CaseClassName]
You can try to access the Data value as:
(jsonData \ "log" \ "Response" \ "info" \ "Data").as[Data]
same for Metadata

How to deserialize dynamic Json with pure Kotlin Serialization

I am investigating the use of Kotlin Serialization in my latest project
The Json I have to contend with is as follows:-
{
"countryCodeMapping": {
"ssd": [
{
"name": "South Sudan",
"alpha2": "ss",
"alpha3": "ssd"
}
],
"nic": [
{
"name": "Nicaragua",
"alpha2": "ni",
"alpha3": "nic"
}
],
"fin": [
{
"name": "Finland",
"alpha2": "fi",
"alpha3": "fin"
}
],
"cck": [
{
"name": "Cocos (Keeling) Islands",
"alpha2": "cc",
"alpha3": "cck"
}
],
"tun": [
{
"name": "Tunisia",
"alpha2": "tn",
"alpha3": "tun"
}
],
"uga": [
{
"name": "Uganda",
"alpha2": "ug",
"alpha3": "uga"
}
],
....
"myDescriptions": {
"1": {
"groupId": 1,
"description": "cvbxcvbxcvbxbxb",
"myTypes": [
"cxcvbxb",
"xcvxcbxxx",
"cvbxcvbxcxvcb"
]
},
"2": {
"groupId": 2,
"description": "vxvbxbxbxbx",
"myTypes": [
"xcvb",
"xcvbxcvb"
]
},
"3": {
"groupId": 3,
"description": "xcvbxcvbxcvbxcvb",
"myTypes": [
"xcvbxcvbxcvbx"
]
},
"4": {
"groupId": 4,
"description": "xcvbxcbxcvbxcb",
"myTypes": [
"xcvbxcxcvb"
]
},
"5": {
"groupId": 5,
"description": "xcvbxcvbxcvbxcvbx",
"myTypes": [
"xcvbxcvbxc",
"xcvbxcvbxcvbxb"
]
},
"6": {
"groupId": 6,
"description": "dfgsdfgsdgsdfgsdgsg",
"myTypes": [
I must decode the entire Json string, however I started with the countryCodeMapping
section as follows:-
#Serializable
data class Meta(
#SerialName("countryCodeMapping")
val mapping: CountryCodeMapping
)
#Serializable
data class CountryCodeMapping(val ssd: List<Country>)
#Serializable
data class Country(
#SerialName("name")
val name: String,
#SerialName("alpha2")
val alpha2: String,
#SerialName("alpha3")
val alpha3: String
)
val countryMap: Meta = Json {
isLenient = true
ignoreUnknownKeys = true
allowStructuredMapKeys = true
}.decodeFromString<Meta>(metaJson)
This works, however I obviously only decode Sudan
countryMap = Meta(mapping=CountryCodeMapping(ssd=[Country(name=South Sudan, alpha2=ss, alpha3=ssd)]))
What data class models do I require to enable me to achieve my goal?
UPDATE
The changes I had to make (Based on answer comment) were
#Serializable
data class Meta(
#SerialName("countryCodeMapping")
val mapping: Map<String, List<Country>>
)
#Serializable
data class Country(
#SerialName("name")
val name: String? = null,
#SerialName("alpha2")
val alpha2: String? = null,
#SerialName("alpha3")
val alpha3: String? = null
)
You can just use a regular map here:
val mapping: Map<String, List<Country>>
When you have JSON objects like { "someKey": ..., "otherKey": ... }, you have mainly 2 options to represent them:
use a regular class with properties
use a generic Map<String, ...>
You current approach uses option 1 (with CountryCodeMapping class, and hardcoded ssd property). This approach is best when the keys are fixed and well known.
Approach 2 is better when the keys are dynamic and all the values have the same type, which is your case here.
To implement approach 2, use what #broot suggested: a Map<String, List<Country>> instead of the CountryCodeMapping class:
#Serializable
data class Meta(
#SerialName("countryCodeMapping")
val mapping: Map<String, List<Country>>
)
#Serializable
data class Country(
#SerialName("name")
val name: String,
#SerialName("alpha2")
val alpha2: String,
#SerialName("alpha3")
val alpha3: String
)

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)])])

Convert struct to json array instead of json object

I apologise in advance if this question is considered too easy or whatever; this is the first time I'm writing anything in go. I have two structs (simplified for this question)
type A struct {
Content string
}
type B struct {
Element A `json:"0"`
Children []B `json:"1"`
}
I want to encode a value of type B into JSON, but instead of returning an object it should return a json array
For example:
What I get:
[
{
"0":{
"Content":"AAA"
},
"1":[
{
"0":{
"Content":"BBB"
},
"1":[
{
"0":{
"Content":"CCC"
},
"1":[]
},
{
"0":{
"Content":"DDD"
},
"1":[]
}
}
]
]
}
]
What I need:
[
{"Content": "AAA"},
[
[
{"Content": "BBB"},
[
{"Content": "CCC"},
[]
]
],
[
{"Content": "DDD"},
[]
]
]
]
I could do this by manually iterating through it, but I would hope that there's an integrated way to do it
You may do so by implementing json.Marshaler interface in B.
For example: https://play.golang.org/p/fT1WlQ5Raz
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Content string
}
type B struct {
Element A
Children []B
}
// MarshalJSON implements json.Marshaler
func (b B) MarshalJSON() ([]byte, error) {
return json.Marshal([]interface{}{
b.Element,
b.Children,
})
}
func main() {
root := B{
Element: A{Content: "AAA"},
Children: []B{
B{
Element: A{Content: "BBB"},
Children: []B{
B{Element: A{Content: "CCC"}, Children: []B{}},
B{Element: A{Content: "DDD"}, Children: []B{}},
},
},
},
}
content, _ := json.MarshalIndent(root, "", " ")
fmt.Printf("%s\n", content)
}
Results:
[
{
"Content": "AAA"
},
[
[
{
"Content": "BBB"
},
[
[
{
"Content": "CCC"
},
[]
],
[
{
"Content": "DDD"
},
[]
]
]
]
]
]
i think you need a slice of interface to wrap both A and B struct into a single slice
please take a look at my snippet here https://play.golang.org/p/c0xldskKyz

Put Data in mutlple branch of Array : Json Transformer ,Scala Play

i want to add values to all the arrays in json object.
For eg:
value array [4,2.5,2.5,1.5]
json =
{
"items": [
{
"id": 1,
"name": "one",
"price": {}
},
{
"id": 2,
"name": "two"
},
{
"id": 3,
"name": "three",
"price": {}
},
{
"id": 4,
"name": "four",
"price": {
"value": 1.5
}
}
]
}
i want to transform the above json in
{
"items": [
{
"id": 1,
"name": "one",
"price": {
"value": 4
}
},
{
"id": 2,
"name": "two",
"price": {
"value": 2.5
}
},
{
"id": 3,
"name": "three",
"price": {
"value": 2.5
}
},
{
"id": 4,
"name": "four",
"price": {
"value": 1.5
}
}
]
}
Any suggestions on how do i achieve this. My goal is to put values inside the specific fields of json array. I am using play json library throughout my application. What other options do i have instead of using json transformers.
You may use simple transformation like
val prices = List[Double](4,2.5,2.5,1.5).map(price => Json.obj("price" -> Json.obj("value" -> price)))
val t = (__ \ "items").json.update(
of[List[JsObject]]
.map(_.zip(prices).map(o => _._1 ++ _._2))
.map(JsArray)
)
res5: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"items":[{"id":1,"name":"one","price":{"value":4}},{"id":2,"name":"two","price":{"value":2.5}},{"id":3,"name":"three","price":{"value":2.5}},{"id":4,"name":"four","price":{"value":1.5}}]},/items)
I suggest using classes, but not sure this fits to your project because it's hard to guess how your whole codes look like.
I put new Item manually for simplicity. You can create items using Json library :)
class Price(val value:Double) {
override def toString = s"{value:${value}}"
}
class Item(val id: Int, val name: String, val price: Price) {
def this(id: Int, name: String) {
this(id, name, null)
}
override def toString = s"{id:${id}, name:${name}, price:${price}}"
}
val price = Array(4, 2.5, 2.5, 1.5)
/** You might convert Json data to List[Item] using Json library instead. */
val items: List[Item] = List(
new Item(1, "one"),
new Item(2, "two"),
new Item(3, "three"),
new Item(4, "four", new Price(1.5))
)
val valueMappedItems = items.zipWithIndex.map{case (item, index) =>
if (item.price == null) {
new Item(item.id, item.name, new Price(price(index)))
} else {
item
}
}