plokhotnyuk / jsoniter-scala

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

How to create a codec when the logic for generating JSON is already provided #1186

Closed DavidPerezIngeniero closed 3 months ago

DavidPerezIngeniero commented 3 months ago

I want to write a custom codec for only serializing.

class SomeObject { .... }

I have an external API that already supplies a function to generate JSON:

object SomeObject {
  def writeToJson(o: SomeObject): String
  def writeToJson(o: SomeObject), out: OutputStream: Unit
}

The good news is that I have the work nearly done. The bad news, is that I don't know how to integrate it with the logic of JsonWriter, that is designed to supply small pieces of JSON.

One idea, is to parse the generated JSON string and call the different methods of JsonWriter.

Antoher idea is to call writeBytes() for each character of the JSON string.

plokhotnyuk commented 3 months ago

@DavidPerezIngeniero Hi, David!

If I understand your requirements correctly then just need to call core package methods writeToString and writeToStream correspondingly, like here:

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

import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._
import java.io.OutputStream

class SomeObject(val id: Int, val data: String)

object SomeObject {
  implicit val codec: JsonValueCodec[SomeObject] = JsonCodecMaker.make

  def writeToJson(o: SomeObject): String = writeToString(o)
  def writeToJson(o: SomeObject, out: OutputStream): Unit = writeToStream(o, out)
}

val obj = SomeObject(1, "XXX")

println(SomeObject.writeToJson(obj))
SomeObject.writeToJson(obj, System.out)
DavidPerezIngeniero commented 3 months ago

I have some Scala classes that have embedded SomeObject, and I only need to write a custom codec for SomeObject. SomeObject is not easily serializable with make, because it is a Java class with many Objects inside and complex structures and several variants. I'd like to create a custom codec that uses the provided API. The problem is that writeByte() is private.

given JsonValueCodec[SomeObject] with
   def encodeValue(x: SomeObject, out: JsonWriter): Unit =
         // send to `out` by using 3rd party logic.
        val json = x.writeToJson().getBytes
        json.foreach { byte =>
              // The problem is that "writeByte()" is private.
              out.writeByte(byte)
        }
   def decodeValue(in: JsonReader, default: SomeObject): : SomeObjec =
    throw NotImplementedError()
  def nullValue: SomeObject = null

I don't know if using writeByte() is the best way to go, supposing it were public.

I'd like to use jsoniter for my classes except for SomeObject thanks to a custom codec. Example:

case class MyClass(
   a: List[SomeObject], 
   b: Int, 
   c: Map[Int, String], ....
)
given JsonValueCodec[MyClass] = make
plokhotnyuk commented 3 months ago

For your case I see 2 options: 1) create Scala case class with an auto-generated codec that mimic your JSON representation for SomeObject as much as possible, print codec's code using the following given given CodecMakerConfig.PrintCodec = new CodecMakerConfig.PrintCodec {} before the make call then copy and refactor it to a custom codec for SomeObject; 2) use 3rd-party logic to serialize SomeObject into an array and use out.writeRawVal(jsonBytes) to copy it into JsonWriter's output.

DavidPerezIngeniero commented 3 months ago

Thanks, writeRawVal() is just what I need. Option 1 is error-prone, and makes me rewrite complex code that already works. With so many writeXXXX() methods in JsonWriter I haven't seen it.