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.