0

I got json like {"name":"susan","age":25},and a hint to json keyset like "name:String,age:Int",how to create a HList from that json?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
xrz
  • 13
  • 4
  • Can you use a library? or it has to be done by hand? if you can use a library have yu already selected that library or can we suggest one? Also, are you sure an `HList` is your end goal or maybe a **case class**? – Luis Miguel Mejía Suárez Sep 28 '20 at 13:07
  • want to parse json dynamically to hlist – xrz Sep 28 '20 at 13:08
  • 1
    Shapeless is compile safe, JSON isn't. Just to be clear, there is nothing "automatic" you can do (I.e. You can't make something that will magically guess the types of a JSON String) – Nick Sep 28 '20 at 13:10
  • but i got a hint of json value type,it's a text like "name:String,age:Int",how to use that to specify type?@Nick – xrz Sep 28 '20 at 13:15
  • 1
    Here there is a brief of Dave Gurnell´s shapeless book(chapter 5): https://stackoverflow.com/a/62177781/6802156 – Emiliano Martinez Sep 28 '20 at 13:15
  • @LuisMiguelMejíaSuárez I want to convert json values to Tuple or case class,don't know how to do that – xrz Sep 28 '20 at 13:16
  • 1
    I would recommend you to take a look to any json library like **circe**. – Luis Miguel Mejía Suárez Sep 28 '20 at 13:21
  • 1
    That sounds like another problem. It may be better to expand this question with all details, or maybe close this one and open a new one to pick more attention. Anyways, I do not know Flink so I can not help anymore, sorry. – Luis Miguel Mejía Suárez Sep 28 '20 at 13:30
  • https://circe.github.io/circe/ – Dmytro Mitin Sep 28 '20 at 13:34
  • @LuisMiguelMejíaSuárez do u know how to convert json to HList with that hint "name:String,age:Int"? – xrz Sep 28 '20 at 13:41
  • Yeah a library like **circe** reads a json a converts it into an `HList` then it can convert that into a **case class**, not sure how to use that with **Flink** or even if that is recommended. – Luis Miguel Mejía Suárez Sep 28 '20 at 13:48
  • thank you@LuisMiguelMejíaSuárez – xrz Sep 28 '20 at 13:56
  • 1
    @LuisMiguelMejíaSuárez "*library like circe reads a json a converts it into an HList then it can convert that into a case class*" A type of hlist (`String :: Int :: HNil`) or case class (`A` for `case class A(s: String, i: Int)`) has to be known at compile time. But hint `"name:String,age:Int"` seems to be a runtime String. – Dmytro Mitin Sep 28 '20 at 14:20
  • 1
    @DmytroMitin yeah, I really did not understand what exactly OP wanted to do. So that is why I suggested opening a new question with more details. – Luis Miguel Mejía Suárez Sep 28 '20 at 14:45
  • 1
    @DmytroMitin I guess he wants to parse a string to case class. If it is not in the correct format - he has to deal with that as with error (Either.left, Option.none or an exception). – Artem Sokolov Sep 28 '20 at 16:44
  • @ArtemSokolov Yeah, I guess I understood, thanks, see the answer. – Dmytro Mitin Sep 28 '20 at 17:07
  • @xrz Actually, I guess we do someting strange. We use highly type-level libraries aimed to static type safety and then run them at runtime with reflection ignoring type safety and getting actually `List[Any]` (`HList`). See update of my answer. – Dmytro Mitin Sep 28 '20 at 22:17
  • @DmytroMitin thank you so much,but my end goal is tuple or case class,can you see my other question?[linke](https://stackoverflow.com/questions/64111895/dynamically-parse-json-in-flink-map) – xrz Sep 29 '20 at 02:15
  • @xrz If you goal is a tuple or case class why is it written "How to convert JSON to scala shapeless.hlist?", "how to create a HList from that json?" in your question? It's not difficult to modify my answer in order to have a tuple or case class rather than HList. But again, similarly to having `HList` and not having `String :: Int :: HNil` you'll have `(Any, Any)` and not have `(String, Int)`. – Dmytro Mitin Sep 29 '20 at 02:20
  • @DmytroMitin Thanks again,I'm new with scala reflect,how to modify you answer to have a tuple or case class? – xrz Sep 29 '20 at 02:29
  • @xrz See update. – Dmytro Mitin Sep 29 '20 at 02:45

1 Answers1

1

Based on the code you added and then deleted, it seems, having a runtime-string hint "name:String,age:Int" and runtime-string json {"name":"susan","age":25}, you want to get an HList with runtime reflection. You can do this as follows

import shapeless.HList
import scala.reflect.runtime
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val tb = runtime.currentMirror.mkToolBox()

val jsonStr = """{"name":"susan","age":25}"""
val hint = "name:String,age:Int"
val classType = tb.define(tb.parse(s"case class Test($hint)").asInstanceOf[ImplDef]).asClass.toType
val hlist = tb.eval(q"""
  import io.circe.generic.auto._
  import io.circe.parser.decode
  val classInstance = decode[$classType]($jsonStr)

  import shapeless.Generic
  Generic[$classType].to(classInstance.toOption.get)
""").asInstanceOf[HList]
println(hlist) // susan :: 25 :: HNil

Please notice that you do everything at runtime so you'll have no access to type String :: Int :: HNil at compile time and hlist has static type just HList (not String :: Int :: HNil) and HList is actually not better than just List[Any].

build.sbt

libraryDependencies ++= Seq(
  scalaOrganization.value % "scala-reflect"  % scalaVersion.value,
  scalaOrganization.value % "scala-compiler" % scalaVersion.value,
  "com.chuusai" %% "shapeless" % "2.4.0-M1",
  "io.circe" %% "circe-core"    % "0.13.0",
  "io.circe" %% "circe-parser"  % "0.13.0",
  "io.circe" %% "circe-generic" % "0.13.0"
)

Actually, I guess we do someting strange. We use highly type-level libraries (shapeless, circe) aimed to static type safety and then run them at runtime with reflection ignoring type safety and getting actually List[Any] (HList).

I guess that if List[Any] (list of field values) is enough for you then you just need to use a more runtime library. For example, with json4s

import org.json4s.{JInt, JObject, JString, JValue}
import org.json4s.jackson.JsonMethods._

val jsonStr: String = """{"name":"susan","age":25}"""
val json: JValue = parse(jsonStr) //JObject(List((name,JString(susan)), (age,JInt(25))))
val l: List[JValue] = json.asInstanceOf[JObject].obj.map(_._2) //List(JString(susan), JInt(25))
val res: List[Any] = l.map {
  case JString(s) => s
  case JInt(n)    => n
} //List(susan, 25)

build.sbt

libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.6.9"

Actually the same can be done with Circe, just with parse instead of decode[A]

import io.circe.{Json, JsonNumber}
import io.circe.parser.parse

val jsonStr: String = """{"name":"susan","age":25}"""
val json: Json = parse(jsonStr).toOption.get //{"name":"susan","age":25}
val l: List[Json] = json.asObject.get.values.toList //List("susan", 25)
val res: List[Any] = l.map(_.fold[Any](null, null, (_: JsonNumber).toInt.get, identity[String], null, null)) //List(susan, 25)

If you need an instance of case class or a tuple rather than HList replace

tb.eval(q"""
  import io.circe.generic.auto._
  import io.circe.parser.decode
  val classInstance = decode[$classType]($jsonStr)

  import shapeless.Generic
  Generic[$classType].to(classInstance.toOption.get)
""").asInstanceOf[HList] // susan :: 25 :: HNil

with

tb.eval(q"""
  import io.circe.generic.auto._
  import io.circe.parser.decode
  decode[$classType]($json).toOption.get
""").asInstanceOf[Product] //Test(susan,25)

or

tb.eval(q"""
  import io.circe.generic.auto._
  import io.circe.parser.decode
  val classInstance = decode[$classType]($json)

  import shapeless.Generic
  Generic[$classType].to(classInstance.toOption.get).tupled
""").asInstanceOf[Product] //(susan,25)

correspondingly.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66