How do you create Json object with values of different types?
Asked Answered
M

2

6

How do you create Json object with values of different types ?

I'm using spray-json

Here is the code

val images : List[JsObject] = fetchImageUrls(url).map((url: String) => {
  JsObject(List(
        "link_path" -> JsString(url),
        "display_name" -> JsString("image"),
        "size" -> JsString(""),
        "modified" -> JsString(""),
        "thumbnail" -> JsString(url),
        "filename" -> JsString("image"),
        "is_dir" -> JsBoolean(x = false),
        "thumb_exists" -> JsBoolean(x = true)) )
  })

val jsonAst: JsObject = JsObject(List(
  "client" -> JsString("urlimages"),
  "view" -> JsString("thumbnails"),
  "contents" -> JsArray(images)
))

It works but looks really heavy. Is there a way to define json with code like this ?

val images : List[List[(String, Any)]] = fetchImageUrls(url).map((url: String) => {
  List(
    "link_path" -> url,
    "display_name" -> "image",
    "size" -> "",
    "modified" -> "",
    "thumbnail" -> url,
    "filename" -> "image",
    "is_dir" -> false,
    "thumb_exists" -> true)
})

val jsonAst = List(
  "client" -> "urlimages",
  "view" -> "thumbnails",
  "contents" -> images
).toJson

It doesn't work saying that

Cannot find JsonWriter or JsonFormat type class for List[(String, Object)]
    ).toJson
      ^

Which I get, type of each field is not defined at compile time. But why wouldn't it work if serializer does pattern matching anyway ?

Thanks!

Markham answered 1/5, 2013 at 6:46 Comment(1)
The Library might support this. But if not, have an implicit function that takes a String and converts it into JsStringDetradetract
C
1

You are going for the wrong approach here. For consistency purposes I would strongly encourage you to use a case class.

Say you have this

case class Image(
    url: String,
    size: Double,
    properties: Map[String][String]
    optionalProperty: Option[String]
    // etc.
);

And then you use parse and decompose to deal with this.

val image = parse(jsonString).extract[Image]; // extracts an Image from JSON.
val jsonForImage: JValue = decompose(image);  // serializes an Image to JSON.

And if you want to serialize a List[Image] to JSON:

def serialize(images: List[Image]) : JValue = {
    for (image <- images) 
       yield decompose(image);
};

To parse a list of images from JSON:

val images: List[Image] = parse(jsonString).extract[List[Image]];

Using Option[SomeType] in the Image case class will deal with missing/optional parameters automatically.

Coagulate answered 1/5, 2013 at 11:17 Comment(0)
P
4

I agree with @alex23 that a case class based approach will be better. Using spray-json, you would first define your case class structure as well as an extension of DefaultJsonProtocol to describe the case classes you want to be able to serialize. That would look like this:

case class Image(link_path:String, display_name:String, size:Option[String], 
  modified:Option[String], thumbnail:String, filename:String, is_dir:Boolean, thumb_exists:Boolean)
object Image  

case class UrlImages(client:String, view:String, contents:List[Image])
object UrlImages

object MyJsonProtocol extends DefaultJsonProtocol {
  implicit val imageFormat = jsonFormat8(Image.apply)
  implicit val urlImagesFormat = jsonFormat3(UrlImages.apply)
}  

Then, a modified version of your example would look like this:

import MyJsonProtocol._
val images : List[Image] = fetchImageUrls(url).map((url: String) => {
  Image(url, "image", None, None, url, "image", false, true)      
})

val jsonAst = UrlImages("urlimages", "thumbnails", images).toJson

The reason why you were seeing that error is that spray-json does not know how to serialize the Lists of tuples you are creating. If you really want to use that structure and not go the case class route, then you could look into providing a custom serializer for List[(String,Any)]. Check out the section in the spray-json docs titled "Providing JsonFormats for other Types". If you want to go this route and need more help, let me know.

Perrotta answered 1/5, 2013 at 11:58 Comment(0)
C
1

You are going for the wrong approach here. For consistency purposes I would strongly encourage you to use a case class.

Say you have this

case class Image(
    url: String,
    size: Double,
    properties: Map[String][String]
    optionalProperty: Option[String]
    // etc.
);

And then you use parse and decompose to deal with this.

val image = parse(jsonString).extract[Image]; // extracts an Image from JSON.
val jsonForImage: JValue = decompose(image);  // serializes an Image to JSON.

And if you want to serialize a List[Image] to JSON:

def serialize(images: List[Image]) : JValue = {
    for (image <- images) 
       yield decompose(image);
};

To parse a list of images from JSON:

val images: List[Image] = parse(jsonString).extract[List[Image]];

Using Option[SomeType] in the Image case class will deal with missing/optional parameters automatically.

Coagulate answered 1/5, 2013 at 11:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.