trouble extracting recursive data structures in scala with json4s - json

I have a json format that consists of map -> map -> ... -> int for an arbitrary number of maps per key. The keys are always strings and the leaf types are always ints. The depth of the map structure varies per key in the map. For example, key "A" could be type Map[String, Int] and key "B" could be type Map[String, Map[String, Int]]. I know I can parse this format successfully as Map[String, Any], but I'm trying to preserve the types to make these structures easier to merge later in the code.
I can't seem to define my nested structure in such a way as to not throw an error on the json4s extract. I'm not quite sure if the problem is in my structure definition or if I'm not doing the json extract correctly.
Here is the code
sealed trait NestedMap[A]
case class Elem[A](val e : A) extends NestedMap[A]
case class NMap[A](val e : Map[String, NestedMap[A]]) extends NestedMap[A]
// XXX this next line doesn't seem to help or hurt
case class Empty extends NestedMap[Nothing]
implicit val formats = DefaultFormats
val s = "{\"1\": 1, \"2\": 1, \"3\": {\"4\": 1}}"
val b = parse(s).extract[NMap[Int]]
Here is the error that always comes up
org.json4s.package$MappingException: No usable value for e
Expected object but got JNothing
Do I need to add another value that extends NestedMap? Am I going about this entirely wrong? Any help is much appreciated.

By default, a tree like yours is expanded to to a different json.
import org.json4s._
import org.json4s.native.Serialization
import org.json4s.native.Serialization.{read, write}
import org.json4s.native.JsonMethods._
implicit val formats = Serialization.formats(NoTypeHints)
sealed trait Elem
case class Leaf(val e:Int) extends Elem
case class Tree(val e:Map[String, Elem]) extends Elem
scala> val t = Tree(Map("1"->Leaf(1),"2"->Leaf(2),"3"->Tree(Map("4"->Leaf(4)))))
t: Tree = Tree(Map(1 -> Leaf(1), 2 -> Leaf(2), 3 -> Tree(Map(4 -> Leaf(4)))))
scala> write(t)
res0: String = {"e":{"1":{"e":1},"2":{"e":2},"3":{"e":{"4":{"e":4}}}}}
scala> val jt = parse(res0)
jt: org.json4s.JValue = JObject(List((e,JObject(List((1,JObject(List((e,JInt(1))))), (2,JObject(List((e,JInt(2))))), (3,JObject(List((e,JObject(List((4,JObject(List((e,JInt(4))))))))))))))))
scala> val s = """{"1":1,"2":2, "3":{"4":1}}"""
s: String = {"1":1,"2":2, "3":{"4":1}}
scala> val st = parse(s)
st: org.json4s.JValue = JObject(List((1,JInt(1)), (2,JInt(2)) (3,JObject(List((4,JInt(1)))))))

Related

serialize Map with key and value as case class using circe

Below is the simplified code that has a Map with key and value as case class.
Key and value object are successfully serialized with circe.
But facing challenge to serialize Map[CaseClassKey, CaseClassValue] with circe.
//////case classes and common vals starts here//////
case class Person(personId : Int, phoneNumber : Int)
case class Item(name : String)
case class Basket(items : List[Item], bills : Map[Int, Int])
def createBasketInstance() : Basket = {
val milk = Item("milk")
val coffee = Item("coffee")
val bills = Map(1 -> 20, 2 -> 75)
Basket( List(milk, coffee), bills )
}
val basket = createBasketInstance()
val person = Person(1, 987654)
//////case classes and common vals ends here//////
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._
import io.circe.parser._
//Serializing Person instance that is used as Key in Map
{
implicit val personCodec :Codec[Person] = deriveCodec[Person]
val jsonString = person.asJson.spaces2
println(jsonString)
}
println("-" * 50)
//Serializing Basket instance that is used as Value in Map
{
implicit val itemCodec :Codec[Item] = deriveCodec[Item]
implicit val basketCodec :Codec[Basket] = deriveCodec[Basket]
val jsonString = basket.asJson.spaces2
println(jsonString)
}
println("-" * 50)
//Serializing Map[Person, Basket]
//TODO : not able to make it work
{
implicit val itemCodec :Codec[Item] = deriveCodec[Item]
implicit val basketCodec :Codec[Basket] = deriveCodec[Basket]
val map = Map(person -> basket)
//TODO : How to make below lines work
//val jsonString = map.asJson.spaces2
//println(jsonString)
}
scalafiddle link : https://scalafiddle.io/sf/SkZNa1L/2
Note : Looking to just serialize and deserialize data (Map[Person, Basket]) correctly. How json looks is not really important in this particular case.
Strictly speaking, you are trying to create an invalid json.
Json map structure isn't an arbitrary map, it's an object structure where keys are property names.
https://www.json.org/json-en.html
See also Can we make object as key in map when using JSON?
Update:
I suggest a slight change to your model to get the job done.
Instead of Map use array of objects, each having two properties: key and value
Something like this:
case class Entry(key: Person, value: Basket)
So you can replace Map[Person, Basket] with Seq[Entry], and convert it back to Map if needed.

Scala, spray-json: universal enumeration json formatting

I have such model: two enumerations and one case class with two fields of these enums types:
// see later, why objects are implicit
implicit object Fruits extends Enumeration {
val Apple = Value("apple")
val Orange = Value("orange")
}
implicit object Vegetables extends Enumeration {
val Potato = Value("potato")
val Cucumber = Value("cucumber")
val Tomato = Value("tomato")
}
type Fruit = Fruits.Value
type Vegetable = Vegetables.Value
case class Pair(fruit: Fruit, vegetable: Vegetable)
I want to parse/generate JSONs to/from Pairs with spray-json. I don't want to declare separate JsonFormats for fruits and vegetables. So, I'd like to do something like this:
import spray.json._
import spray.json.DefaultJsonProtocol._
// enum is implicit here, that's why we needed implicit objects
implicit def enumFormat[A <: Enumeration](implicit enum: A): RootJsonFormat[enum.Value] =
new RootJsonFormat[enum.Value] {
def read(value: JsValue): enum.Value = value match {
case JsString(s) =>
enum.withName(s)
case x =>
deserializationError("Expected JsString, but got " + x)
}
def write(obj: enum.Value) = JsString(obj.toString)
}
// compilation error: couldn't find implicits for JF[Fruit] and JF[Vegetable]
implicit val pairFormat = jsonFormat2(Pair)
// expected value:
// spray.json.JsValue = {"fruit":"apple","vegetable":"potato"}
// but actually doesn't even compile
Pair(Fruits.Apple, Vegetables.Potato).toJson
Sadly, enumFormat doesn't produce implicit values for jsonFormat2. If I write manually two implicit declarations before pairFormat for fruits and vegetables formats, then json marshalling works:
implicit val fruitFormat: RootJsonFormat[Fruit] = enumFormat(Fruits)
implicit val vegetableFormat: RootJsonFormat[Vegetable] = enumFormat(Vegetables)
implicit val pairFormat = jsonFormat2(Pair)
// {"fruit":"apple","vegetable":"potato"}, as expected
Pair(Fruits.Apple, Vegetables.Potato).toJson
So, two questions:
How to get rid of these fruitFormat and vegetableFormat declarations?
Ideally it would be great to not make enumeration objects implicit, while keeping enumFormat function generic. Is there a way to achieve this? Maybe, using scala.reflect package or something like that.
You just have to replace enum.Value with A#Value.
Looking at spray-json #200 you can find an example of a well defined implicit enumFormat, slightly modified to leverage implicit enu retrieval:
implicit def enumFormat[T <: Enumeration](implicit enu: T): RootJsonFormat[T#Value] =
new RootJsonFormat[T#Value] {
def write(obj: T#Value): JsValue = JsString(obj.toString)
def read(json: JsValue): T#Value = {
json match {
case JsString(txt) => enu.withName(txt)
case somethingElse => throw DeserializationException(s"Expected a value from enum $enu instead of $somethingElse")
}
}
}
You can't, Scala doesn't allow implicit chaining, because it would lead to combinatorial explosions that would make the compiler too slow.
See https://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html
Scala does not allow two such implicit conversions taking place, however, so one cannot got from A to C using an implicit A to B and another implicit B to C.
You'll have to explicitly produce a JsonFormat[T] for each T you want to use.

How can I define a dynamic base json object?

I would like to design a base trait/class in Scala that can produce the following json:
trait GenericResource {
val singularName: String
val pluralName: String
}
I would inherit this trait in a case class:
case class Product(name: String) extends GenericResource {
override val singularName = "product"
override val pluralName = "products"
}
val car = Product("car")
val jsonString = serialize(car)
the output should look like: {"product":{"name":"car"}}
A Seq[Product] should produce {"products":[{"name":"car"},{"name":"truck"}]} etc...
I'm struggling with the proper abstractions to accomplish this. I am open to solutions using any JSON library (available in Scala).
Here's about the simplest way I can think of to do the singular part generically with circe:
import io.circe.{ Decoder, Encoder, Json }
import io.circe.generic.encoding.DerivedObjectEncoder
trait GenericResource {
val singularName: String
val pluralName: String
}
object GenericResource {
implicit def encodeResource[A <: GenericResource](implicit
derived: DerivedObjectEncoder[A]
): Encoder[A] = Encoder.instance { a =>
Json.obj(a.singularName -> derived(a))
}
}
And then if you have some case class extending GenericResource like this:
case class Product(name: String) extends GenericResource {
val singularName = "product"
val pluralName = "products"
}
You can do this (assuming all the members of the case class are encodeable):
scala> import io.circe.syntax._
import io.circe.syntax._
scala> Product("car").asJson.noSpaces
res0: String = {"product":{"name":"car"}}
No boilerplate, no extra imports, etc.
The Seq case is a little trickier, since circe automatically provides a Seq[A] encoder for any A that has an Encoder, but it doesn't do what you want—it just encodes the items and sticks them in a JSON array. You can write something like this:
implicit def encodeResources[A <: GenericResource](implicit
derived: DerivedObjectEncoder[A]
): Encoder[Seq[A]] = Encoder.instance {
case values # (head +: _) =>
Json.obj(head.pluralName -> Encoder.encodeList(derived)(values.toList))
case Nil => Json.obj()
}
And use it like this:
scala> Seq(Product("car"), Product("truck")).asJson.noSpaces
res1: String = {"products":[{"name":"car"},{"name":"truck"}]}
But you can't just stick it in the companion object and expect everything to work—you have to put it somewhere and import it when you need it (otherwise it has the same priority as the default Seq[A] instances).
Another issue with this encodeResources implementation is that it just returns an empty object if the Seq is empty:
scala> Seq.empty[Product].asJson.noSpaces
res2: String = {}
This is because the plural name is attached to the resource at the instance level, and if you don't have an instance there's no way to get it (short of reflection). You could of course conjure up a fake instance by passing nulls to the constructor or whatever, but that seems out of the scope of this question.
This issue (the resource names being attached to instances) is also going to be trouble if you need to decode this JSON you've encoded. If that is the case, I'd suggest considering a slightly different approach where you have something like a GenericResourceCompanion trait that you mix into the companion object for the specific resource type, and to indicate the names there. If that's not an option, you're probably stuck with reflection or fake instances, or both (but again, probably not in scope for this question).

Making Reads and Writes in Scala Play for lists of custom classes

So I have two classes in my project
case class Item(id: Int, name: String)
and
case class Order(id: Int, items: List[Item])
I'm trying to make reads and writes properties for Order but I get a compiler error saying:
"No unapply or unapplySeq function found"
In my controller I have the following:
implicit val itemReads = Json.reads[Item]
implicit val itemWrites = Json.writes[Item]
implicit val listItemReads = Json.reads[List[Item]]
implicit val listItemWrites = Json.writes[List[Item]]
The code works for itemReads and itemWrites but not for the bottom two. Can anyone tell me where I'm going wrong, I'm new to Play framework.
Thank you for your time.
The "No unapply or unapplySeq function found" error is caused by these two:
implicit val listItemReads = Json.reads[List[Item]]
implicit val listItemWrites = Json.writes[List[Item]]
Just throw them away. As Ende said, Play knows how to deal with lists.
But you need Reads and Writes for Order too! And since you do both reading and writing, it's simplest to define a Format, a mix of the Reads and Writes traits. This should work:
case class Item(id: Int, name: String)
object Item {
implicit val format = Json.format[Item]
}
case class Order(id: Int, items: List[Item])
object Order {
implicit val format = Json.format[Order]
}
Above, the ordering is significant; Item and the companion object must come before Order.
So, once you have all the implicit converters needed, the key is to make them properly visible in the controllers. The above is one solution, but there are other ways, as I learned after trying to do something similar.
You don't actually need to define those two implicits, play already knows how to deal with a list:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> case class Item(id: Int, name: String)
defined class Item
scala> case class Order(id: Int, items: List[Item])
defined class Order
scala> implicit val itemReads = Json.reads[Item]
itemReads: play.api.libs.json.Reads[Item] = play.api.libs.json.Reads$$anon$8#478fdbc9
scala> implicit val itemWrites = Json.writes[Item]
itemWrites: play.api.libs.json.OWrites[Item] = play.api.libs.json.OWrites$$anon$2#26de09b8
scala> Json.toJson(List(Item(1, ""), Item(2, "")))
res0: play.api.libs.json.JsValue = [{"id":1,"name":""},{"id":2,"name":""}]
scala> Json.toJson(Order(10, List(Item(1, ""), Item(2, ""))))
res1: play.api.libs.json.JsValue = {"id":10,"items":[{"id":1,"name":""},{"id":2,"name":""}]}
The error you see probably happens because play uses the unapply method to construct the macro expansion for your read/write and List is an abstract class, play-json needs concrete type to make the macro work.
This works:
case class Item(id: Int, name: String)
case class Order(id: Int, items: List[Item])
implicit val itemFormat = Json.format[Item]
implicit val orderFormat: Format[Order] = (
(JsPath \ "id").format[Int] and
(JsPath \ "items").format[JsArray].inmap(
(v: JsArray) => v.value.map(v => v.as[Item]).toList,
(l: List[Item]) => JsArray(l.map(item => Json.toJson(item)))
)
)(Order.apply, unlift(Order.unapply))
This also allows you to customize the naming for your JSON object. Below is an example of the serialization in action.
Json.toJson(Order(1, List(Item(2, "Item 2"))))
res0: play.api.libs.json.JsValue = {"id":1,"items":[{"id":2,"name":"Item 2"}]}
Json.parse(
"""
|{"id":1,"items":[{"id":2,"name":"Item 2"}]}
""".stripMargin).as[Order]
res1: Order = Order(1,List(Item(2,Item 2)))
I'd also recommend using format instead of read and write if you are doing symmetrical serialization / deserialization.

JSON arrays and Scala Seq

I have scala class like class A(b:Seq[String])
My problem is when I deserialize it from text which have no b field my class contains null. It is possible to force deserealizer to fill with empty Seq?
I use com.fasterxml.jackson.databind.ObjectMapper with com.fasterxml.jackson.module.scala.DefaultScalaModule.
EDIT:
I want solution that fix all such fields without explicitly mentioning full list of them. Ir changing all declarations.
This is not currently supported by Jackson, unfortunately.
You can see the relevant GitHub ticket here: https://github.com/FasterXML/jackson-databind/issues/347
Your best bet is to map null into an empty Seq in the class constructor or in an accessor method:
class A(_b: Seq[String]) {
val b = _b match {
case null => Nil
case bs => bs
}
}
(See also https://stackoverflow.com/a/20655330/8261 for other options)
If you use Spray JSON, then a simple example which doesn't handle the absence of the b field would look like:
import spray.json._
case class A(b: Seq[String])
object Protocol extends DefaultJsonProtocol {
implicit def aFormat = jsonFormat1(A)
}
import Protocol._
val str1 = """{ "b" : [] }""""
val str2 = """{ "b" : ["a", "b", "c"] }""""
val str3 = """{}"""
str1.parseJson.convertTo[A]//successful
str2.parseJson.convertTo[A]//successful
str3.parseJson.convertTo[A]//Deserialization error
In Spray JSON, this can be solved by writing a more detailed protocol format for class A:
import spray.json._
case class A(b: Seq[String])
object Protocol extends DefaultJsonProtocol {
implicit object aFormat extends RootJsonFormat[A] {
override def write(obj: A): JsValue = JsObject("b" -> obj.b.toJson)
override def read(json: JsValue): A = json match {
//Case where the object has exactly one member, 'b'
case JsObject(map) if map.contains("b") && map.size == 1 => A(map("b").convertTo[Seq[String]])
//Case where the object has no members
case JsObject(map) if map.isEmpty => A(Seq())
//Any other json value, which cannot be converted to an instance of A
case _ => deserializationError("Malformed")
}
}
}
import Protocol._
val str1 = """{ "b" : [] }""""
val str2 = """{ "b" : ["a", "b", "c"] }""""
val str3 = """{}"""
str1.parseJson.convertTo[A]//successful
str2.parseJson.convertTo[A]//successful
str3.parseJson.convertTo[A]//successful
I had a similar problem deserializing String arrays with jackson, and found that jackson apparently (have not tried yet) fixed this in jackson-module 2.1.2. It might work for Seq.
https://github.com/FasterXML/jackson-module-scala/issues/48