Scala: how to selectively write fields of an object to json string? - scala

I have a query object:
case class SearchQuery(keyword: String, count: Int, sort: String)
I serialize this object to send it to a restful api to get search response.
Is it possible to not write some of the properties when serializing based on some condition like, if sort is empty I want the json string to be "{keyword: 'whatever', count: 25}" and if sort is non empty then I would like it to be "{keyword: 'whatever', count: 25, sort: 'unitPrice'}". What is best way to achieve this?
I am using lift json for serialization.
Any help is greatly appreciated. Thank you.
Update
val reqHeaders: scala.collection.immutable.Seq[HttpHeader] = scala.collection.immutable.Seq(
RawHeader("accept", "application/json"),
RawHeader("authorization", "sgdg545wf34rergt34tg"),
RawHeader("content-type", "application/json"),
RawHeader("x-customer-id", "45645"),
RawHeader("x-locale-currency", "usd"),
RawHeader("x-locale-language", "en"),
RawHeader("x-locale-shiptocountry", "US"),
RawHeader("x-locale-site", "us"),
RawHeader("x-partner-id", "45sfg45fgd5")
)
implicit val formats = DefaultFormats
val searchObject = net.liftweb.json.Serialization.write(req) //req is search object
val searchObjectEntity = HttpEntity(ContentTypes.`application/json`, searchObject)
val request = HttpRequest(HttpMethods.POST, "https://api.xxxxxxx.com/services/xxxxxxxx/v1/search?client_id=654685", reqHeaders, searchObjectEntity)

In Lift-Json, optional values are not serialized. So if you change your case class to have case class SearchQuery(keyword: String, count: Int, sort: Option[String]), you should get just the behavior you want.
See "Any value can be optional" in
https://github.com/lift/lift/tree/master/framework/lift-base/lift-json

You can make your your sort field optional, as scala gives you a way of handling fields which can be optional and then use Lift Json or Jerkson Json for serialization.
Here is the sample code with Jerkson Json.
case class SearchQuery(keyword: String, count: Int, sort: Option[String])
com.codahale.jerkson.Json.generate(SearchQuery("keyword",1,None))
This will give you output ->
{"keyword":"keyword","count":1}

Related

How to remove null values in list of objects using Circe

I'm trying to encode a list of objects using Circe, something that looks similar to:
val test = Seq(MyObject("hello", None, 1, 2, None)
I'm trying to parse this using Circe:
test.asJson
But this creates the JSON object:
[
{
name: "hello",
someVal: null,
someNum: 1,
anotherNum: 2,
anotherVal: null
}
]
I've tried running asJson with .dropNullValues, but that doesn't seem to access the null values inside of the object. Is there a way to drop the null values inside of the objects?
I'm expecting something more like this:
[
{
name: "hello",
someNum: 1,
anotherNum: 2
}
]
Circe provides the function deepDropNullValues in the Json class.
Example: test.asJson.deepDropNullValues
You see field: null because circe turns Option[T] to t.asJson on Some[T] and JsonNull on None, and default case class encoder just puts all fields to the JsonObject. In a way that circe uses to encode sealed trait family, it may use these null fields to distinguish classes like
sealed trait Foo
case class Bar(a: Option[String])
case class Baz(a: Option[String], b: Option[String])
So, if you really want to drop this information and need one-way conversion with information loss, you can map resulting Json to drop all and every null field with code like that:
implicit val fooEnc: Encoder[Foo] = deriveEncoder[Foo].mapJsonObject{jsonObj => jsonObj.filter{case (k,v) => !v.isNull}}
However, you should write such a custom codec for any class you want to drop null fields. To post-process your json, you can use fold on resulting json:
val json: Json = ???
json.fold[Json](
Json.Null,
Json.fromBoolean,
{_.asJson},
{_.asJson},
{_.asJson},
{jsonObj => jsonObj.filter{case (k,v) => !v.isNull}.asJson}
)
or implement a custom folder.
The answer that was suggested by #iva-kmm is good, but it can be done better!
This mechanic is already implemented in circe, just call it:
implicit val fooEnc: Encoder[Foo] = deriveEncoder[Foo].mapJson(_.dropNullValues) // or _.deepDropNullValues

Sangria-graphql: error when passing in derivedInputObjectType as an mutation argument

I have the following case class with option fields:
case class BusinessUserRow(id: String, firstName: Option[String], lastName: Option[String], email: Option[String])
I am trying to create an inputType Object for Business User Object
val BusinessUserInputType =
deriveInputObjectType[BusinessUserRow](
InputObjectTypeName("input"),
InputObjectTypeDescription("A Business user")
)
and I want to pass this BusinessInputObject as an argument to a addBusinessUser mutation
val businessUserInputArg = Argument("input", BusinessUserInputType)
val Mutation = ObjectType("Mutation", fields[RepoContext, Unit](
Field("addBusinessUser", BusinessUserType,
arguments = businessUserInputArg :: Nil,
resolve = c ⇒ c.ctx.BusinessUserRepo.create(c.arg(businessUserInputArg)))))
But I get the following compilation error:
Type dao.Tables.BusinessUserRow ## sangria.marshalling.FromInput.InputObjectResult cannot be used as an input. Please consider defining an implicit instance of `FromInput` for it.
[error] val businessUserInputArg = Argument("input", BusinessUserInputType)
But All fields in BusinessRow are scalar values. I don't understand what is causing the issue.Is there something I am not seeing?
In order to deserialize the input in the BusinessUserRow case class, you need to provide an instance of FromInput[BusinessUserRow] type class. You can find more docs on it here:
http://sangria-graphql.org/learn/#frominput-type-class
So if you are, for instance, using spray-json, then you need to define JsonFormat for BusinessUserRow
thanks! just adding this line solved my problem:
implicit val businessUserFormat = Json.format[BusinessUserRow]

Play Framework Serial Tuple3

I am experimenting around with Scala and Play. I want to return a Tuple, in this case a Tuple3 but could be Tuple of any size. I want to serialize the Tuple as JSON, but Play doesn't seem to know how to serialize a Tuple.
I'm just trying to do something very simple like the following
def getClient(clientId: Int) = Action {
val result = ("I", "AM", "TUPLE")
Ok(Json.toJson(result))
}
No Json serializer found for type (String, String, String). Try to implement an implicit Writes or Format for this type.
I tried something like this but it only seems to work on Tuple2.
val seq = Seq[(String,String)](("attr1"->"val1"),("attr2"->"val2"))
val s = Json.toJson(seq.map(e => Json.obj((e._1 -> e._2))))
Ok(s).as(JSON)
You can create a case class like this.
case class MyCaseClass(string1: String, string2: String, string3: String)
then you have to add an implicit writer with
implicit val myCaseClassFormat = Json.format[MyCaseClass]
then you can do Json.toJson(MyCaseClass("I", "AM", "TUPLE"))

json4s extractor for partial fields (when we care to preserve the original Json)

When parsing a Json String, there are cases where we are interested in parsing just a subset of the fields (the ones we know we need) and delay any semantic parsing/mapping of the rest of the fields.
E.g. we want inspect the key in a key-value Event for (say) load-balancing, but at that point in time we don't have enough info to completely parse the values (we assume that some application further down the road will know what to do with the values):
val json = """{ "key": {"a": true, "b": 123}, "value": [1,2,3,4] }"""
case class Key(a: Boolean, b: Int)
case class Event(key: Key, value: ???) // value can be any valid Json
import org.json4s._
import org.json4s.native.JsonMethods._
implicit val formats = DefaultFormats
val parsedJson = parse(json)
parsedJson.extract[Event]
The question is how to represent the fields that we don't yet know (or care) to parse? What do we add as the type for value?
Note: One solution is to change the event to be case class Event(key: Key). This will work, but it will completely ignore the value, which we ideally want to keep so we can dispatch it properly to another service. So this solution will not work for us.
parse() parses JSON into json4s AST and returns JValue.
AFAIK You can not parse JSON partially because parsing to AST includes JSON validation and for that you need to parse the whole JSON string to AST tree.
But you can partially extract from AST. Here you have two options.
First. Make value field a JValue to defer extracting. You can do it later by calling extract() on this JValue instance.
case class Event(key: Key, value: JValue)
val event = parsedJson.extract[Event]
val value = event.value.extract[Seq[Int]]
Second. Split Event to two classes and extract them separately.
case class EventKey(key: Key)
case class EventValue(value: Seq[Int])
val parsedJson = parse(json)
val eventKey = parsedJson.extract[EventKey]
val eventValue = parsedJson.extract[EventValue]

Creating a `Decoder` for arbitrary JSON

I am building a GraphQL endpoint for an API using Finch, Circe and Sangria. The variables that come through in a GraphQL query are basically an arbitrary JSON object (let's assume there's no nesting). So for example, in my test code as Strings, here are two examples:
val variables = List(
"{\n \"foo\": 123\n}",
"{\n \"foo\": \"bar\"\n}"
)
The Sangria API expects a type for these of Map[String, Any].
I've tried a bunch of ways but have so far been unable to write a Decoder for this in Circe. Any help appreciated.
The Sangria API expects a type for these of Map[String, Any]
This is not true. Variables for an execution in sangria can be of an arbitrary type T, the only requirement that you have an instance of InputUnmarshaller[T] type class for it. All marshalling integration libraries provide an instance of InputUnmarshaller for correspondent JSON AST type.
This means that sangria-circe defines InputUnmarshaller[io.circe.Json] and you can import it with import sangria.marshalling.circe._.
Here is a small and self-contained example of how you can use circe Json as a variables:
import io.circe.Json
import sangria.schema._
import sangria.execution._
import sangria.macros._
import sangria.marshalling.circe._
val query =
graphql"""
query ($$foo: Int!, $$bar: Int!) {
add(a: $$foo, b: $$bar)
}
"""
val QueryType = ObjectType("Query", fields[Unit, Unit](
Field("add", IntType,
arguments = Argument("a", IntType) :: Argument("b", IntType) :: Nil,
resolve = c ⇒ c.arg[Int]("a") + c.arg[Int]("b"))))
val schema = Schema(QueryType)
val vars = Json.obj(
"foo" → Json.fromInt(123),
"bar" → Json.fromInt(456))
val result: Future[Json] =
Executor.execute(schema, query, variables = vars)
As you can see in this example, I used io.circe.Json as variables for an execution. The execution would produce following result JSON:
{
"data": {
"add": 579
}
}
Here's a decoder that works.
type GraphQLVariables = Map[String, Any]
val graphQlVariablesDecoder: Decoder[GraphQLVariables] = Decoder.instance { c =>
val variablesString = c.downField("variables").focus.flatMap(_.asString)
val parsedVariables = variablesString.flatMap { str =>
val variablesJsonObject = io.circe.jawn.parse(str).toOption.flatMap(_.asObject)
variablesJsonObject.map(j => j.toMap.transform { (_, value: Json) =>
val transformedValue: Any = value.fold(
(),
bool => bool,
number => number.toDouble,
str => str,
array => array.map(_.toString),
obj => obj.toMap.transform((s: String, json: Json) => json.toString)
)
transformedValue
})
}
parsedVariables match {
case None => left(DecodingFailure(s"Unable to decode GraphQL variables", c.history))
case Some(variables) => right(variables)
}
}
We basically parse the JSON, turn it into a JsonObject, then transform the values within the object fairly simplistically.
Although the above answers work for the specific case of Sangria, I'm interested in the original question: What's the best approach in Circe (which generally assumes that all types are known up front) for dealing with arbitrary chunks of Json?
It's fairly common when encoding/decoding Json that 95% of the Json is specified, but the last 5% is some type of "additional properties" chunk which can be any Json object.
Solutions I've played with:
Encode/Decode the free-form chunk as Map[String,Any]. This means you'll have to introduce implicit encoders/decoders for Map[String, Any], which can be done, but is dangerous as that implicit can be pulled into places you didn't intend.
Encode/Decode the free-form chunk as Map[String, Json]. This is the easiest approach and is supported out of the box in Circe. But now the Json serialization logic has leaked out into your API (often you'll want to keep the Json stuff completely wrapped, so you can swap in other non-json formats later).
Encode/Decode to a String, where the string is required to be a valid Json chunk. At least you haven't locked your API into a specific Json library, but it doesn't feel very nice to have to ask your users to create Json chunks in this manual way.
Create a custom trait hierarchy to hold the data (e.g sealed trait Free; FreeInt(i: Int) extends Free; FreeMap(m: Map[String, Free] extends Free; ...). Now you you can create specific encoders/decoders for it. But what you've really done is replicate the Json type hierarchy that already exists in Circe.
I'm leaning more toward option 3. Since it's the most flexible, and will introduce the least dependencies in the API. But none of them are entirely satisfying. Any other ideas?