plokhotnyuk / jsoniter-scala

Scala macros for compile-time generation of safe and ultra-fast JSON codecs + circe booster
MIT License
741 stars 99 forks source link

Handling same field with different value type #1214

Open HelloJeevan opened 5 days ago

HelloJeevan commented 5 days ago

Hey, Actually i am using jsoniter-scala lib but i am having situation like this

    val jsonString1 = """{"name": "foo", "id": "2604"}"""
    val jsonString2 = """{"name": "bar", "id": 2105}"""

Can we desrialize the json request in which we get same field sometime as integer and sometime as string with having same case class...i have tried using Either and AnyVal data type but not able to implement it

case class Person(name: String, id: Either[String, Int])

I am open to all method to handle this kind of situation Thanks

plokhotnyuk commented 5 days ago

@HelloJeevan Thanks for the question!

If you just need to parse both numeric and stringified JSON values as Int then you should define the following custom codec before the make[Person] call:

implicit val customCodecOfInt: JsonValueCodec[Int] = new JsonValueCodec[Int] {
  def decodeValue(in: JsonReader, default: Int): Int =
    if (in.isNextToken('"')) {
      in.rollbackToken()
      in.readStringAsInt()
    } else {
      in.rollbackToken()
      in.readInt()
    }

  def encodeValue(x: Int, out: JsonWriter): Unit = out.writeVal(x)

  def nullValue: Int = 0
}

Also, please, see more advanced example of JavaScript compatible codec for Long here

HelloJeevan commented 5 days ago

Hey @plokhotnyuk thanks for replying really appriciate that Can not we deserialize directly the JSON request through some readFromString method becuase we are already doing that in our code base so that it can in sync with the code we currently have. Can you show me how we can deserilaize the JSON request i have provided that will provide more insight actually i am new to this library

plokhotnyuk commented 2 days ago

Here is a script that tests the proposed solution for you:

//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.31.0"
//> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.31.0"

import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._

case class Person(name: String, id: Int)

implicit val customCodecOfInt: JsonValueCodec[Int] = new JsonValueCodec {
  def decodeValue(in: JsonReader, default: Int): Int =
    if (in.isNextToken('"')) {
      in.rollbackToken()
      in.readStringAsInt()
    } else {
      in.rollbackToken()
      in.readInt()
    }

  def encodeValue(x: Int, out: JsonWriter): Unit = out.writeVal(x)

  def nullValue: Int = 0
}

implicit val personCodec: JsonValueCodec[Person] = JsonCodecMaker.make

val person = try readFromStream[Person](System.in) catch {
  case ex: Throwable => ex.printStackTrace(System.err)
}

println(person)

It accepts JSON string from System.in and prints parsed Person to System.out. Please store that code snippet in some file and use scala-cli to compile and run it for different input in Linux using the following commands:

scala-cli script.sc <<< '{"name": "foo", "id": "2604"}'
scala-cli script.sc <<< '{"name": "foo", "id": 2604}'

Expected output for both inputs is the same:

Person(foo,2604)