I'm trying to parse JSON where same field can be either array or object. Same as, specific field can be either string or number. Please consider examples below.
Empty object
{
"technicalData": {}
}
Collection with field being either string or number
{
"technicalData": [
{
"techValueString": "0.173"
},
{
"techValueString": 0.173
}
]
}
How can I do it with Circe mapping to Scala classes accepting Nil when data is {}?
case class Response(technicalData: Seq[TechnicalData])
case class TechnicalData(techValueString: String)
Thanks.
Here is less verbose solution applying Circe's Decoders
case class Response(technicalData: Seq[TechnicalData])
case class TechnicalData(techValueString: String)
class StringToResponse() extends (String => Response) {
implicit val responseDecoder: Decoder[Response] = Decoder.instance { c =>
for {
technicalData <- c.downField("technicalData").focus match {
case None => Right(Nil)
case Some(seq) => seq.asArray match {
case None => Right(Nil)
case Some(_) => c.get[Seq[TechnicalData]]("technicalData")
}
}
} yield {
Response(technicalData)
}
}
implicit val technicalDataDecoder: Decoder[TechnicalData] = (
Decoder.instance(_.get[String]("techValueString")).or(
Decoder.instance(_.get[Double]("techValueString").map(_.toString))
)
) mapN TechnicalData
override def apply(body: String): Response = {
decode[Response](body) match {
case Right(response) => response
case Left(e) => throw new RuntimeException(e)
}
}
}
Hope this will help someone who would come across similar problem.
This is a really verbose way of resolving your problem but I hope it has the advantage of letting you identify, or even rectify, every limit cases, which you might need:
import io.circe._
import io.circe.parser.parse
case class Response(technicalData: Seq[TechnicalData])
case class TechnicalData(techValueString: String)
val stringAsJson1 = """{
"technicalData": {}
}"""
val stringAsJson2 = """{
"technicalData": [
{
"techValueString": "0.173"
},
{
"techValueString": 0.173
}
]
}"""
def manageTechnicalDataAsArray(jsonArray: Vector[io.circe.Json]): Response = {
Response(
jsonArray.map(cell => {
val value = cell.asObject
.getOrElse(throw new Exception("technicalData as a array should have each cell as an object"))
.apply("techValueString")
.getOrElse(throw new Exception("techValueString should be a key of any cell under technicalData array"))
TechnicalData(value.asNumber
.map(_.toString)
.getOrElse(
value.asString
.getOrElse(throw new Exception("techValueString value should be either string or number"))
)
)
}
)
)
}
def manageTechnicalDataAsObject(jsonObject: io.circe.JsonObject): Response = {
jsonObject.toIterable match {
case empty if empty.isEmpty => Response(Nil)
case _ => throw new Exception("technicalData when object should be empty")
}
}
def parseResponse(jsonAsString: String): Response = {
parse(jsonAsString).getOrElse(Json.Null)
.asObject
.map(_("technicalData")
.getOrElse(throw new Exception("the json should contain a technicalData key"))
.arrayOrObject(throw new Exception("technicalData should contain either an objet or array"),
manageTechnicalDataAsArray,
manageTechnicalDataAsObject
)
).getOrElse(throw new Exception("the json should contain an object at top"))
}
println(parseResponse(stringAsJson1))
println(parseResponse(stringAsJson2))
I might come with a shorter version soon but less indicative on limit cases. You can explore them with tweaked version of a good json of yours.
Hope it helps.
EDIT: Here is a shorter and cleaner solution than above, which come after #Sergey Terentyev well found one. Well, it might be less readeable somehow, but it tends to do the same thing with more or less way to handle limit cases:
// Structure part
case class TechnicalData(techValueString: String)
object TechnicalData {
def apply[T](input: T) = new TechnicalData(input.toString)
}
case class Response(technicalData: Seq[TechnicalData])
// Decoding part
import io.circe.{Decoder, parser, JsonObject, JsonNumber}
import io.circe.Decoder.{decodeString, decodeJsonNumber}
def tDDGenerator[C : Decoder]: Decoder[TechnicalData] = Decoder.forProduct1("techValueString")(TechnicalData.apply[C])
implicit val technicalDataDecoder: Decoder[TechnicalData] = tDDGenerator[String].or(tDDGenerator[JsonNumber])
implicit val responseDecoder: Decoder[Response] = Decoder[JsonObject]
.emap(_("technicalData").map(o => Right(o.as[Seq[TechnicalData]].fold(_ => Nil, identity)))
.getOrElse(Right(Nil))
.map(Response.apply))
// Test part
val inputStrings = Seq(
"""{
| "technicalData": [
| {
| "techValueString": "0.173"
| },
| {
| "techValueString": 0.173
| }
| ]
|}
""".stripMargin,
"""{
| "technicalData": {}
|}
""".stripMargin
)
inputStrings.foreach(parser.decode[Response](_).fold(println,println))
Related
I have an incoming JSON data that looks like below:
{"id":"1000","premium":29999,"eventTime":"2021-12-22 00:00:00"}
Now, I have created a class that will accept this record and will check whether the data type of the incoming record is according to the data types defined in the case class. However, when I am calling the method it is always calling the Failure part of the match case.
case class Premium(id: String, premium: Long, eventTime: String)
class Splitter extends ProcessFunction[String, Premium] {
val outputTag = new OutputTag[String]("failed")
def fromJson[T](json: String)(implicit m: Manifest[T]): Either[String, T] = {
Try {
println("inside")
lazy val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.readValue[T](json)
} match {
case Success(x) => {
Right(x)
}
case Failure(err) => {
Left(json)
}
}
}
override def processElement(i: String, context: ProcessFunction[String, Premium]#Context, collector: Collector[Premium]): Unit = {
fromJson(i) match {
case Right(data) => {
collector.collect(data)
println("Good Records: " + data)
}
case Left(json) => {
context.output(outputTag, json)
println("Bad Records: " + json)
}
}
}
}
Based on the sample record above, it should pass the Success value but no matter what I pass, it always enters the Failure part. What else is missing?
I am using Scala 2.11.12 and I tried examples from this link and this link but no luck.
I am looking for a way to define a Reads which allows me to map a JSON containing the following structure:
{
"offers": [
[
{
"id": "1234",
(etc.)
}
]
]
}
to model such case class TransportOffer(offers: List[Offer])
Unfortunately I haven't been able to do this yet. This is my code:
implicit val transportOfferReads: Reads[TransportOffer] = (
(JsPath \ "offers").read[List[List[Offer]]].flatMap(r => r.flatten)
)(TransportOffer.apply _)
In this case the flattening is not possible, as flatMap expects another Reads. How would I wrap the flattened List into another Reads?
Or is there a simpler way?
I'll present 3 options:
Flattening in a short reads:
case class Offer(id: String)
object Offer {
implicit val format: OFormat[Offer] = Json.format[Offer]
}
case class TransportOffer(offers: List[Offer])
object TransportOffer {
implicit val transportOfferReads: Reads[TransportOffer] =
(JsPath \ "offers").read[List[List[Offer]]].map(x => TransportOffer(x.flatten))
}
Then calling:
Json.parse(jsonString).validate[TransportOffer].foreach(println)
outputs:
TransportOffer(List(Offer(1234)))
Code run at Scastie
Explicitly writing Reads:
implicit val transportOfferReads: Reads[TransportOffer] = (json: JsValue) => {
json \ "offers" match {
case JsUndefined() =>
JsError("offers undefined")
case JsDefined(value) =>
value.validate[List[List[Offer]]].map(x => TransportOffer(x.flatten))
}
Code run at Scastie.
First transform the json, into the model you'd like. For that define a transformer:
val jsonTransformer = (__ \ "offers").json
.update(__.read[JsArray].map{o => {
JsArray(o.value.flatMap(_.asOpt[JsArray].map(_.value)).flatten)
}})
Then, assuming we have the case classes and their formatters:
case class Offer(id: String)
object Offer {
implicit val format: OFormat[Offer] = Json.format[Offer]
}
case class TransportOffer(offers: List[Offer])
object TransportOffer {
implicit val format: OFormat[TransportOffer] = Json.format[TransportOffer]
}
We can call:
Json.parse(jsonString).transform(jsonTransformer) match {
case JsSuccess(value, _) =>
value.validate[TransportOffer].foreach(println)
case JsError(errors) =>
println(errors)
???
}
Output is:
TransportOffer(List(Offer(1234)))
Code run at Scastie.
I can handle {"phone": "2312323", "message": "This is test"} in Akka-Http using
entity(as[Message]) { message =>
val f: Future[Any] = service ask message
onSuccess(f) { r: Any =>
{
r match {
case Some(message: Message) =>
complete(HttpEntity(ContentTypes.`application/json`, message.toJson.prettyPrint))
case _ =>
complete(StatusCodes.NotFound)
}
}
}
}
but how can I handle
[
{"phone": "2312323", "message": "This is test"},
{"phone": "2312321213", "message": "This is test 1212"}
]
Hi You can do like this:
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
// domain model
final case class Info(phone: String, message: String)
final case class MessageInfo(messages: List[Info])
// collect your json format instances into a support trait:
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat = jsonFormat2(Info)
implicit val orderFormat = jsonFormat1(MessageInfo) // contains List[Info]
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives with JsonSupport {
// format: OFF
val route =
post {
entity(as[MessageInfo]) { messageInfo => // will unmarshal JSON to Order
val messageCount = messageInfo.messages.size
val phoneNumbers = messageInfo.items.map(_.phone).mkString(", ")
complete(s"Messages $messageCount with the contact number: $phoneNumbers")
}
}
// format: ON
}
For more info please see here:
https://doc.akka.io/docs/akka-http/current/common/json-support.html
You can use a simpler way, with less code (and use gson):
val gson = new Gson
...
entity(as[String]) { messageStr =>
val f: Future[Any] = service ask message
onSuccess(f) { r: Any =>
{
r match {
case Some(messageStringValue: String) =>
val messagesList = gson.fromJson(messageStringValue, classOf[Array[Message]])
val messageCount = messagesList.size
val phoneNumbers = messagesList.map(_.phone).mkString(", ")
complete(s"Messages $messageCount with the contact number: $phoneNumbers")
case _ =>
complete(StatusCodes.NotFound)
}
}
}
}
I'm working through a slight variation of Sangria's Getting Started, using Akka HTTP. I'm attempting to use json4s-jackson as the serializaltion lib, but am running in to some trouble getting the response I want.
Specifically, the serialized response I get is the JSON version of the (StatusCode, Node) tuple:
{
"_1": {
"allowsEntity": true,
"defaultMessage": "OK",
"intValue": 200,
"reason": "OK"
},
"_2": {
"data": {
"foo": {
"id": "1",
"name": "Foo"
}
}
}
}
The data portion is correct, but obviously I just want that and not the first element of the serialized tuple.
I'm using akka-http-json4s, so my trait with route looks like:
case class GraphQlData(query: String, operation: Option[String])
trait FooController {
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
val fooRoutes = post {
entity(as[GraphQlData]) { data =>
QueryParser.parse(data.query) match {
// query parsed successfully, time to execute it!
case Success(queryAst) =>
complete {
Executor
.execute(
SchemaDefinition.FooSchema,
queryAst,
new FooService,
operationName = data.operation
)
.map(OK -> _)
.recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
}
// can't parse GraphQL query, return error
case Failure(error) =>
complete(BadRequest -> error.getMessage)
}
}
}
implicit def executionContext: ExecutionContext
}
For the life of me I can't figure out what's wrong. I've been looking at sangria-akka-http-example but it seems to be exactly the same, with the exception of using spray-json instead of json4s.
Ideas? Thanks!
Ah, figured it out. I neglected to add
import sangria.marshalling.json4s.jackson._
to the trait defining the route. Adding it does the trick.
Just wanted to provide a quick update to this answer for the full GraphQLRequest. Now the variables are included in the request.
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s._
import org.json4s.JsonAST.JObject
import sangria.marshalling.json4s.jackson._
case class GQLRequest(query: String, operationName: Option[String], variables: JObject)
trait SomeJsonSupport extends Json4sSupport {
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
}
trait GraphQLResource extends SomeJsonSupport{
implicit val timeout:Timeout
implicit val system:ActorSystem
import system.dispatcher
def graphqlRoute: Route =
(post & path("graphql")) {
entity(as[GQLRequest]) { requestJson =>
println(s"This is the requestJson = $requestJson")
graphQLEndpoint(requestJson)
}
} ~
get {
println(s"This is working")
getFromResource("graphiql.html")
}
def graphQLEndpoint(requestJson: GQLRequest): Route = {
val route = QueryParser.parse(requestJson.query) match {
case Success(query) =>
println(s"This is the query $query")
val vars = requestJson.variables match {
case jObj:JObject => jObj
case _ => JObject(List.empty)
}
val futureJValue = Executor.execute(clientSchema,
query,
NclhGqlRequest(this),
operationName = requestJson.operationName,
variables = vars)
val futureTupleStatusCodeJValue = futureJValue.map(OK -> _).recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
complete(futureTupleStatusCodeJValue)
case Failure(error) =>
complete(BadRequest, error.getMessage)
}
route
}
I am trying to implement generic method in Scala operating on database using Quill.io library. Type T will be only case classes what works with Quill.io.
def insertOrUpdate[T](inserting: T, equality: (T,T) => Boolean)(implicit ctx: Db.Context): Unit = {
import ctx._
val existingQuery = quote {
query[T].filter { dbElement: T =>
equality(dbElement, inserting)
}
}
val updateQuery = quote {
query[T].filter { dbElement =>
equality(dbElement, lift(inserting))
}.update(lift(inserting))
}
val insertQuery = quote { query[T].insert(lift(inserting)) }
val existing = ctx.run(existingQuery)
existing.size match {
case 1 => ctx.run(updateQuery)
case _ => ctx.run(insertQuery)
}
}
But I am getting two types of compile error
Error:(119, 12) Can't find an implicit `SchemaMeta` for type `T`
query[T].filter { dbElement: T =>
Error:(125, 33) Can't find Encoder for type 'T'
equality(dbElement, lift(inserting))
How can I modify my code to let it work?
As I said in the issue that #VojtechLetal mentioned in his answer you have to use macros.
I added code implementing generic insert or update in my example Quill project.
It defines trait Queries that's mixed into context:
trait Queries {
this: JdbcContext[_, _] =>
def insertOrUpdate[T](entity: T, filter: (T) => Boolean): Unit = macro InsertOrUpdateMacro.insertOrUpdate[T]
}
This trait uses macro that's implementing your code with minor changes:
import scala.reflect.macros.whitebox.{Context => MacroContext}
class InsertOrUpdateMacro(val c: MacroContext) {
import c.universe._
def insertOrUpdate[T](entity: Tree, filter: Tree)(implicit t: WeakTypeTag[T]): Tree =
q"""
import ${c.prefix}._
val updateQuery = ${c.prefix}.quote {
${c.prefix}.query[$t].filter($filter).update(lift($entity))
}
val insertQuery = quote {
query[$t].insert(lift($entity))
}
run(${c.prefix}.query[$t].filter($filter)).size match {
case 1 => run(updateQuery)
case _ => run(insertQuery)
}
()
"""
}
Usage examples:
import io.getquill.{PostgresJdbcContext, SnakeCase}
package object genericInsertOrUpdate {
val ctx = new PostgresJdbcContext[SnakeCase]("jdbc.postgres") with Queries
def example1(): Unit = {
val inserting = Person(1, "")
ctx.insertOrUpdate(inserting, (p: Person) => p.name == "")
}
def example2(): Unit = {
import ctx._
val inserting = Person(1, "")
ctx.insertOrUpdate(inserting, (p: Person) => p.name == lift(inserting.name))
}
}
P.S. Because update() returns number of updated records your code can be simplified to:
class InsertOrUpdateMacro(val c: MacroContext) {
import c.universe._
def insertOrUpdate[T](entity: Tree, filter: Tree)(implicit t: WeakTypeTag[T]): Tree =
q"""
import ${c.prefix}._
if (run(${c.prefix}.quote {
${c.prefix}.query[$t].filter($filter).update(lift($entity))
}) == 0) {
run(quote {
query[$t].insert(lift($entity))
})
}
()
"""
}
As one of the quill contributors said in this issue:
If you want to make your solution generic then you have to use macros because Quill generates queries at compile time and T type has to be resolved at that time.
TL;DR The following did not work either, just playing
Anyway... just out of curiosity I tried to fix the issue by following the error which you mentioned. I changed the definition of the function as:
def insertOrUpdate[T: ctx.Encoder : ctx.SchemaMeta](...)
which yielded the following log
[info] PopulateAnomalyResultsTable.scala:71: Dynamic query
[info] case _ => ctx.run(insertQuery)
[info]
[error] PopulateAnomalyResultsTable.scala:68: exception during macro expansion:
[error] scala.reflect.macros.TypecheckException: Found the embedded 'T', but it is not a case class
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:34)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:28)
It starts promising, since quill apparently gave up on static compilation and made the query dynamic. I checked the source code of the failing macro and it seems that quill is trying to get a constructor for T which is not known in the current context.
For more details see my answer Generic macro with quill or implementation
CrudMacro:
Complete project you will find on quill-generic
package pl.jozwik.quillgeneric.quillmacro
import scala.reflect.macros.whitebox.{ Context => MacroContext }
class CrudMacro(val c: MacroContext) extends AbstractCrudMacro {
import c.universe._
def callFilterOnIdTree[K: c.WeakTypeTag](id: Tree)(dSchema: c.Expr[_]): Tree =
callFilterOnId[K](c.Expr[K](q"$id"))(dSchema)
protected def callFilterOnId[K: c.WeakTypeTag](id: c.Expr[K])(dSchema: c.Expr[_]): Tree = {
val t = weakTypeOf[K]
t.baseClasses.find(c => compositeSet.contains(c.asClass.fullName)) match {
case None =>
q"$dSchema.filter(_.id == lift($id))"
case Some(base) =>
val query = q"$dSchema.filter(_.id.fk1 == lift($id.fk1)).filter(_.id.fk2 == lift($id.fk2))"
base.fullName match {
case `compositeKey4Name` =>
q"$query.filter(_.id.fk3 == lift($id.fk3)).filter(_.id.fk4 == lift($id.fk4))"
case `compositeKey3Name` =>
q"$query.filter(_.id.fk3 == lift($id.fk3))"
case `compositeKey2Name` =>
query
case x =>
c.abort(NoPosition, s"$x not supported")
}
}
}
def createAndGenerateIdOrUpdate[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val id = $entity.id
val q = $filter
val result = run(
q.updateValue($entity)
)
if (result == 0) {
run($dSchema.insertValue($entity).returningGenerated(_.id))
} else {
id
}
"""
}
def createWithGenerateIdOrUpdateAndRead[K: c.WeakTypeTag, T: c.WeakTypeTag](entity: Tree)(dSchema: c.Expr[_]): Tree = {
val filter = callFilter[K, T](entity)(dSchema)
q"""
import ${c.prefix}._
val id = $entity.id
val q = $filter
val result = run(
q.updateValue($entity)
)
val newId =
if (result == 0) {
run($dSchema.insertValue($entity).returningGenerated(_.id))
} else {
id
}
run($dSchema.filter(_.id == lift(newId)))
.headOption
.getOrElse(throw new NoSuchElementException(s"$$newId"))
"""
}
}