I have a json array of settings like so:
[
{
"name": "Company Name",
"key": "company_name",
"default": "Foo"
}, {
"name": "Deposit Weeks",
"key": "deposit_weeks",
"default": 6
}, {
"name": "Is VAT registered",
"key": "vat_registered",
"default": false
}
]
I want to parse this into a Seq of Setting objects. I have tried to define my json format by using a trait and defining the different case classes according to the data type in the json object:
sealed trait Setting
case class StringSetting(name: String, key: String, default: String) extends Setting
case class IntSetting(name: String, key: String, default: Int) extends Setting
case class BoolSetting(name: String, key: String, default: Boolean) extends Setting
Now I try to parse the json:
val json = Json.parse(jsonStr)
implicit val jsonFormat: Format[Setting] = Json.format[Setting]
val result = Try(json.as[Seq[Setting]])
Here I get a compile error:
Error:(19, 61) No unapply or unapplySeq function found
implicit val jsonFormat: Format[Setting] = Json.format[Setting]
Is there a way to map each setting to its appropriate case class?
The naive approach would be to provide Reads[Setting](if your aim just to convert json to object) so that JSON deserializer able to build the right variant of Setting.
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val settingReads: Reads[Setting] = (__ \ "default").read[String].map[Setting](StringSetting) |
(__ \ "default").read[Int].map[Setting](IntSetting) |
(__ \ "default").read[Boolean].map[Setting](BoolSetting)
However, this would not work if you have same type for 'default' in different sub classes. In this case JSON deserializer unable to distinguish
between those two case classes.
Another approach is to use play json variant library.
import julienrf.variants.Variants
sealed trait Setting
case class StringSetting(name: String, key: String, default: String) extends Setting
case class IntSetting(name: String, key: String, default: Int) extends Setting
case class BoolSetting(name: String, key: String, default: Boolean) extends Setting
object Setting {
implicit val format: Format[Setting] = Variants.format[Setting]
}
Variant.format provides both read and writes for Setting. Make sure that assignment of 'implicit val format' should happen after all possible subclass has been declared.
For more information regarding play json variant library click here
Related
I'm trying to parse a json string of search results of the following form:
"""
{
"metadata": [
"blah",
"unimportant"
],
"result": [
{
"type": "one",
"title": "this",
"weird": "attribute unique to this type",
"other": 7
},
{
"type": "two",
"title": "that",
"another_weird": "a different attribute unique to this second type",
"another": "you get the idea"
},
{
"type": "one",
"title": "back to this type, which has the same fields as the other element of this type",
"weird": "yep",
"other": 8
}
...
]
}
"""
There is a known, fixed number of result element types (given by the type field), each of which correspond to a unique schema. For a given search request, there can be any number of each type returned in any order (up to some fixed total).
Writing out the case classes and Reads implicits for each type is easy enough, but my question is around the best way to handle the variability in the result sequence... pattern matching seems the obvious choice, but I'm just not sure where or how with the play framework. Thanks in advance!
EDIT: Per the suggestion in the comments, I gave it a go by attempting to read the result sequence as subtypes of a common base trait, but that didn't quite work. The following compiles, but doesn't parse the example json correctly. Any additional suggestions are welcome.
sealed trait Parent {def title: String}
case class One(`type`: String, title: String, weird: String, other: Int) extends Parent
case class Two(`type`: String, title: String, another_weird: String, another: String) extends Parent
case class SearchResponse(result: Seq[Parent], metadata: Seq[String])
implicit val oneReader = Json.reads[One]
implicit val twoReader = Json.reads[Two]
implicit val parentReader = Json.reads[Parent]
implicit val searchReader = (
(__ \ "result").read[Seq[Parent]] and (__ \ "metadata").read[Seq[String]]
)(SearchResponse.apply _)
val searchResult = Json.fromJson[SearchResponse](json)
print(searchResult)
Define implicit JsonConfiguration
import play.api.libs.json._
sealed trait Parent {
def title: String
}
case class One(title: String, weird: String, other: Int) extends Parent
case class Two(title: String, another_weird: String, another: String) extends Parent
case class SearchResponse(result: Seq[Parent], metadata: Seq[String])
implicit val cfg = JsonConfiguration(
discriminator = "type",
typeNaming = JsonNaming(_.toLowerCase)
)
implicit val oneReader = Json.reads[One]
implicit val twoReader = Json.reads[Two]
implicit val parentReader = Json.reads[Parent]
implicit val searchReader = Json.reads[SearchResponse]
val searchResult = Json.fromJson[SearchResponse](json)
println(searchResult)
// JsSuccess(SearchResponse(List(One(this,attribute unique to this type,7), Two(that,a different attribute unique to this second type,you get the idea), One(back to this type, which has the same fields as the other element of this type,yep,8)),List(blah, unimportant)),)
https://www.playframework.com/documentation/2.8.x/ScalaJsonAutomated#Custom-Naming-Strategies
Suppose I have the following case classes that need to be serialized as JSON objects using circe:
#JsonCodec
case class A(a1: String, a2: Option[String])
#JsonCodec
case class B(b1: Option[A], b2: Option[A], b3: Int)
Now I need to encode val b = B(None, Some(A("a", Some("aa")), 5) as JSON but I want to be able to control whether it is output as
{
"b1": null,
"b2": {
"a1": "a",
"a2": "aa"
},
"b3": 5
}
or
{
"b2": {
"a1": "a",
"a2": "aa"
},
"b3": 5
}
Using Printer's dropNullKeys config, e.g. b.asJson.noSpaces.copy(dropNullKeys = true) would result in omitting Nones from output whereas setting it to false would encode Nones as null (see also this question). But how can one control this setting on a per field basis?
The best way to do this is probably just to add a post-processing step to a semi-automatically derived encoder for B:
import io.circe.{ Decoder, JsonObject, ObjectEncoder }
import io.circe.generic.JsonCodec
import io.circe.generic.semiauto.{ deriveDecoder, deriveEncoder }
#JsonCodec
case class A(a1: String, a2: Option[String])
case class B(b1: Option[A], b2: Option[A], b3: Int)
object B {
implicit val decodeB: Decoder[B] = deriveDecoder[B]
implicit val encodeB: ObjectEncoder[B] = deriveEncoder[B].mapJsonObject(
_.filter {
case ("b1", value) => !value.isNull
case _ => true
}
)
}
And then:
scala> import io.circe.syntax._
import io.circe.syntax._
scala> B(None, None, 1).asJson.noSpaces
res0: String = {"b2":null,"b3":1}
You can adjust the argument to the filter to remove whichever null-valued fields you want from the JSON object (here I'm just removing b1 in B).
It's worth noting that currently you can't combine the #JsonCodec annotation and an explicitly defined instance in the companion object. This isn't an inherent limitation of the annotation—we could check the companion object for "overriding" instances during the macro expansion, but doing so would make the implementation substantially more complicated (right now it's quite simple). The workaround is pretty simple (just use deriveDecoder explicitly), but of course we'd be happy to consider an issue requesting support for mixing and matching #JsonCodec and explicit instances.
Circe have added a method dropNullValues on Json that uses what Travis Brown mentioned above.
def dropNulls[A](encoder: Encoder[A]): Encoder[A] =
encoder.mapJson(_.dropNullValues)
implicit val entityEncoder: Encoder[Entity] = dropNulls(deriveEncoder)
When using Json4s it was very clear you can explicitly ignore specific fields. However, I don't see any documentation on how to ignore all unknown fields.
In Jackson, I would use the following annotation:
import com.fasterxml.jackson.annotation._
#JsonIgnoreProperties(ignoreUnknown=true)
case class MyClass(string: String)
How do I do this with either the Jackson or Native version of Json4s?
I'm not quite sure, if I get you here, but Json4s (at least the org.json4s version) ignores additional fields by default.
import org.json4s.DefaultFormats
import org.json4s.native.JsonMethods._
implicit val formats = DefaultFormats
case class Mailserver(url: String, username: String, password: String)
val json = parse(
"""
{
"url": "imap.yahoo.com",
"username": "myusername",
"password": "mypassword",
"additional": "field"
}
"""
)
val m = json.extract[Mailserver]
println(m.url)
println(m.username)
println(m.password)
This works fine.
I have a json model, where contents of certain attribute depend on the other attribute. Something like this:
"paymentMethod": "CREDIT_CARD",
"metaData": {
"cardType": "VISA",
"panPrefix": "",
"panSuffix": "",
"cardHolder": "",
"expiryDate": ""
}
So when paymentMethod equals to CREDIT_CARD, the metadata object will contain attributes as described. In case of other payment method, there'll be different metadata.
I want to handle this situation in a future-proof way. What I'm trying to do is to not parse the metadata field right away, but keep it somehow "unparsed" until I've parsed the paymentMethod field. Then I'd take the metadata and applied appropriate parsing approach.
However I don't know which type to use for a Scala class field for such "late parsed" attributes. I've tried String, JsonInput, JObject, and they all are not suitable (either don't compile or can't be parsed). Any ideas which type can I use? Or, in other words:
case class CreditCardMetadata(
cardType: String,
panPrefix: String,
panSuffix: String,
cardHolder: String,
expiryDate: String)
case class PaypalMetadata(...) // etc.
case class PaymentGatewayResponse(
paymentMethod: String,
metadata: ???)
You could create a CustomSerializer to parse the metadata directly. Something like :
case class PaymentResponse(payment: Payment, otherField: String)
sealed trait Payment
case class CreditCardPayment(cardType: String, expiryDate: String) extends Payment
case class PayPalPayment(email: String) extends Payment
object PaymentResponseSerializer extends CustomSerializer[PaymentResponse]( format => (
{
case JObject(List(
JField("paymentMethod", JString(method)),
JField("metaData", metadata),
JField("otherField", JString(otherField))
)) =>
implicit val formats = DefaultFormats
val payment = method match {
case "CREDIT_CARD" => metadata.extract[CreditCardPayment]
case "PAYPAL" => metadata.extract[PayPalPayment]
}
PaymentResponse(payment, otherField)
},
{ case _ => throw new UnsupportedOperationException } // no serialization to json
))
Which can be used as:
implicit val formats = DefaultFormats + PaymentResponseSerializer
val json = parse("""
{
"paymentMethod": "CREDIT_CARD",
"metaData": {
"cardType": "VISA",
"expiryDate": "2015"
},
"otherField": "hello"
}
""")
val json2 = parse("""
{
"paymentMethod": "PAYPAL",
"metaData": {
"email": "foo#bar.com"
},
"otherField": "world"
}
""")
val cc = json.extract[PaymentResponse]
// PaymentResponse(CreditCardPayment(VISA,2015),hello)
val pp = json2.extract[PaymentResponse]
// PaymentResponse(PayPalPayment(foo#bar.com),world)
You can use a Map[String, String].
It will contain anything you may need.
The answer by Peter Neyens has inspired me to implement my own solution. It's not as generic as his, but in my case I needed something really simple and ad-hoc. Here's what I've done:
It's possible to define a case class with the field of unknown type is represented by a JObject type. Something like this:
case class PaymentGatewayResponse(
default: Boolean,
paymentMethod: String,
visibleForCustomer: Boolean,
active: Boolean,
metaData: JObject)
When such json is parsed into such case class, this field is not parsed immediately, but contains all the necessary information. Then it's possible parse it in a separate step:
case class CreditCardMetadata(
cardType: String,
cardObfuscatedNumber: String,
cardHolder: String,
expiryDate: String)
val response: PaymentGatewayResponse = doRequest(...)
response.map { r =>
r.paymentMethod match {
case "CREDIT_CARD" => r.metaData.extract[CreditCardMetadata]
case unsupportedType: String => throw new UnsupportedPaymentMethodException(unsupportedType)
}
}
I am trying to process an Ajax POST request in Play Framework 2.1.3. The post data is a JSON object and has a tree structure like:
{ id: "a", name: "myname", kids : [{ id: "a1", name : "kid1", kids: []}, {id: "a2", name: "kid2", kids: [{id: "aa1", name :"grandkid", kids: []}]}]
I would like to nest the 'children' arbitrarily deep. The class I would have in mind is like this (I realize the recursiveness can be problematic):
case class Person {
id: String,
name: String,
kids: Array[Person]
}
The format I would have in mind:
implicit val personFormat:Format[Person] = Json.format[Person]
Play is throwing errors on my Format that I wrote:
type mismatch; found : controllers.Resources.Person required: Array[controllers.Resources.Person]
I am aware that Play has a Tree structure. I couldn't find examples/documentation on how to tie that to JSON reads.
Any help is highly appreciated, thanks
You will need a recursive val, something like:
implicit val jsonReads: Reads[Person] = ((__ \ "id").read[String] and (__ \ "name").read[String] and (__ \ "kids").read[Seq[Person]])(apply _)
(I've changed the collection type from Array to Seq because it's more general and allows you to change your implementation without affecting downline code.)
This is using the syntax documented here.
The only way that I see this working is using either JsArray or Array[String] instead of Array[Person] in your Person case class. The JSON Macro Inception can only generate reads + writes code for primitives and lists, maps, and arrays for objects for which there already exist implicit JSON read + write code. Essentially you can't have a case class that references itself.
package models
import play.api.libs.json._
case class Person(
id : String,
name : String,
kids : JsArray
)
object Person extends ((String,String,JsArray) => Person) {
implicit val jsonFormat = Json.format[Person]
}