Search code examples
jsonscalaencodingargonaut

Complex Encoding of multiple nested classes using scala argonaut


What is wrong with the MainClassEncodeJson method below?

Have been following the example for Implicit JSON conversion with Scala http://lollyrock.com/articles/scala-implicit-conversion/

This question is similar to: Encoding nested classes using scala argonaut

The difference being an additional layer/s of nesting.

Am getting the following error from the compiler:

Cannot resolve reference EncodeJson with such signature

Thanks for in advance.

Have included the Encoder and Decoder for completeness


object ImplicitConversion {

  case class MainClass (txid: String,hat: Hat,version: Int,bot: Bot, time: Int, locktime: Int)
  case class Hat (value: Float,n: Int,nxt: Nxt)
  case class Nxt (typetx: String,reqsigs: Int,addresses: List[Address])
  case class Bot (base: String,sequence: Int)
  case class Address (address: String)


  // implicit conversion with argonaut
  implicit def MainClassEncodeJson: EncodeJson[MainClass] =
    EncodeJson((m: MainClass) =>
      ("txid" := m.txid) ->:
      ("hat" := Json (
          ("value" := m.hat.value),
          ("n" := m.hat.n),
          ("nxt" := Json (
            ("typetx" := m.hat.nxt.typetx),
            ("reqsigs" := m.hat.nxt.reqsigs),
            ("addresses" := m.hat.nxt.addresses)
          )
            )  ->: jEmptyObject
        )
          ) ->: jEmptyObject
      ("version" := m.version) ->:
      ("bot" := Json (
              ("base" := m.bot.base)
              ("sequence" := m.bot.sequence)
        )
          ) ->: jEmptyObject
        ("time" := m.time) ->: 
        ("locktime" := m.locktime) ->:
    )

  implicit def MainClassDecodeJson: DecodeJson[MainClass] =
    DecodeJson(c => for {
      txid <- (c --\ "txid").as[String]
      hat <- (c --\ "hat").as[Json]
      version <- (c --\ "version").as[Int]
      bot <- (c --\ "bot").as[Json]
      time <- (c --\ "time").as[Int]
      locktime <- (c --\ "locktime").as[Int]

      // extract data from hat
      value <- (hat.acursor --\ "value").as[Float]
      n <- (hat.acursor --\ "n").as[Int]
      nxt <- (hat.acursor --\ "nxt").as[Json]

      // extract data from nxt
      typetx <- (nxt.acursor --\ "typetx").as[String]
      reqsigs <- (nxt.acursor --\ "reqsigs").as[Int]
      addresses <- (nxt.acursor --\ "addresses").as[List[Address]]

      // extract data from bot
      base <- (bot.acursor --\ "base").as[String]
      sequence <- (bot.acursor --\ "sequence").as[Int]

    } yield MainClass(txid, hat(value, n, Nxt(typetx, reqsigs, addresses)), 
                     version, Bot(base, sequence), time, locktime)

}

Solution

  • Using version 6.1 with Scalaz 7.1.x I got the following to compile using the CodecJson\[_\] and casecodecN functions.

    import scalaz._, Scalaz._
    import argonaut._, Argonaut._
    
    object ImplicitConversion {
    
      case class MainClass( txid: String
                          , hat: Hat
                          , version: Int
                          , bot: Bot
                          , time: Int
                          , locktime: Int)
    
      case class Hat( value: Float
                    , n: Int
                    , nxt: Nxt)
    
      case class Nxt( typetx: String
                    , reqsigs: Int
                    , addresses: List[Address])
    
      case class Bot( base: String
                    , sequence: Int)
    
      case class Address(address: String)
    
    
      implicit val botCodec: CodecJson[Bot] =
        casecodec2(Bot.apply, Bot.unapply)("base", "sequence")
    
      implicit val addressCodec: CodecJson[Address] =
        casecodec1(Address.apply, Address.unapply)("address")
    
      implicit val nxtCodec: CodecJson[Nxt] =
        casecodec3(Nxt.apply, Nxt.unapply)("typetx", "reqsigs", "addresses")
    
      implicit val hatCodec: CodecJson[Hat] =
        casecodec3(Hat.apply, Hat.unapply)("value", "n", "nxt")
    
      implicit val mainClassCodec: CodecJson[MainClass] =
        casecodec6(MainClass.apply, MainClass.unapply)("txid", "hat", "version", "bot", "time", "locktime")
    
    }
    

    My build.sbt looks like this:

    name := "stackoverflow"
    
    scalaVersion := "2.11.7"
    
    val scalazVersion = "7.1.0"
    
    val argonautVersion = "6.1"
    
    libraryDependencies ++= Seq(
      "org.scalaz"      %% "scalaz-core"    % scalazVersion,
      "io.argonaut"     %% "argonaut"       % argonautVersion,
    )
    

    By using this way of defining the encode/decode - when using simple case classes to embody JSON object literals - I think we end up with simpler code to maintain due to improved readability and reduction of moving parts (it's all declarative as opposed to following the for-comprehension logic). I also find it is a more composable definition of the problem so I can start out small without worrying about what wraps inner case classes.

    You will need to put the CodecJson[_] implicits in order of need (i.e. define the inner case class codec implicits before outer ones that use them) or you will get compile-time warnings.

    There is also a helpful example in the QuickStart documentation on the Argonaut website.