plokhotnyuk / jsoniter-scala

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

Parsing of Sealed Trait Fails When Changing Order of Fields in JSON #1155

Closed antosha417 closed 2 months ago

antosha417 commented 2 months ago

Description:

I encountered an issue when parsing a JSON object into a sealed trait. It fails depending on the order of the fields. Parser expects type field to be the first one.

According to RFC 8259, "An object is an unordered collection of zero or more name/value pairs." This implies that the order of fields in a JSON object should not affect the parsing process.

Example code:

you can run it with scala-cli https://gist.github.com/antosha417/5c54816fe442e8b1792155164fcd202c

//> using scala "3.3.3"
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core:2.30.1"
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros:2.30.1"

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

@main def main(): Unit = {
  sealed trait Shape

  case class Circle(radius: Double) extends Shape
  case class Rectangle(width: Double, height: Double) extends Shape
  case class Square(side: Double) extends Shape

  given shapeCodec: JsonValueCodec[Shape] = JsonCodecMaker.make

  val validJson = """{"type":"Circle","radius":1.0}"""
  val jsonInDifferentOrder = """{"radius":1.0,"type":"Circle"}"""

  println(s"Parsing shape from JSON: $validJson")
  val shapeFromJson = readFromString[Shape](validJson)
  println(s"Successfully parsed shape from JSON: $shapeFromJson")

  println(
    s"Parsing shape from JSON with fields in different order: $jsonInDifferentOrder"
  )
  Try(readFromString[Shape](jsonInDifferentOrder)) match {
    case Success(shape) =>
      println("Successfully parsed shape with fields in different order")
    case Failure(e) =>
      println(s"Failed to parse shape with fields in different order: $e")
  }

}

Output:

Parsing shape from JSON: {"type":"Circle","radius":1.0}
Successfully parsed shape from JSON: Circle(1.0)
Parsing shape from JSON with fields in different order: {"radius":1.0,"type":"Circle"}
Failed to parse shape with fields in different order: com.github.plokhotnyuk.jsoniter_scala.core.JsonReaderException: expected key: "type", offset: 0x00000009
, buf:
+----------+-------------------------------------------------+------------------+
|          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f | 0123456789abcdef |
+----------+-------------------------------------------------+------------------+
| 00000000 | 7b 22 72 61 64 69 75 73 22 3a 31 2e 30 2c 22 74 | {"radius":1.0,"t |
| 00000010 | 79 70 65 22 3a 22 43 69 72 63 6c 65 22 7d       | ype":"Circle"}   |
+----------+-------------------------------------------------+------------------+
plokhotnyuk commented 2 months ago

Use JsonCodecMaker.make(CodecMakerConfig.withRequireDiscriminatorFirst(false)) instead of just JsonCodecMaker.make if you have trusted input or not too deep nesting of sum types with such form of discriminator.

antosha417 commented 2 months ago

@plokhotnyuk Thank you very much for your response! Your assistance is greatly appreciated. It's fantastic to see such quick and effective support for the project.

Thanks again for your help!

Closing the issue. Maybe it is a great idea to add more documentation on CodecMakerConfig so there will be less questions like this.