Parsing a simple array with Spray-json
Asked Answered
O

2

7

I'm trying (and failing) to get my head around how spray-json converts json feeds into objects. If I have a simple key -> value json feed then it seems to work ok but the data I want to read comes in a list like this:

[{
    "name": "John",
    "age": "30"
},
{
    "name": "Tom",
    "age": "25"
}]

And my code looks like this:

package jsontest

import spray.json._
import DefaultJsonProtocol._

object JsonFun {

  case class Person(name: String, age: String)
  case class FriendList(items: List[Person])

  object FriendsProtocol extends DefaultJsonProtocol {
    implicit val personFormat = jsonFormat2(Person)
    implicit val friendListFormat = jsonFormat1(FriendList)
  }

  def main(args: Array[String]): Unit = {

    import FriendsProtocol._

    val input = scala.io.Source.fromFile("test.json")("UTF-8").mkString.parseJson

    val friendList = input.convertTo[FriendList]

    println(friendList)
  }

}    

If I change my test file so it just has a single person not in an array and run val friendList = input.convertTo[Person] then it works and everything parses but as soon as I try and parse an array it fails with the error Object expected in field 'items'

Can anyone point me the direction of what I'm doing wrong?

Outherod answered 15/2, 2015 at 19:8 Comment(1)
Can you post an example of the JSON you're trying to decode?Evasive
O
9

Well, as is often the way immediately after posting something to StackOverflow after spending hours trying to get something working, I've managed to get this to work.

The correct implementation of FriendsProtocol was:

object FriendsProtocol extends DefaultJsonProtocol {
  implicit val personFormat = jsonFormat2(Person)
  implicit object friendListJsonFormat extends RootJsonFormat[FriendList] {
    def read(value: JsValue) = FriendList(value.convertTo[List[Person]])
    def write(f: FriendList) = ???
  } 
}

Telling Spray how to read / write (just read in my case) the list object is enough to get it working.

Hope that helps someone else!

Outherod answered 15/2, 2015 at 19:47 Comment(0)
A
2

To make the Friend array easier to use extend the IndexedSeq[Person]trait by implementing the appropriate apply and length methods. This will allow the Standard Scala Collections API methods like map, filter and sortBy directly on the FriendsArray instance itself without having to access the underlying Array[Person] value that it wraps.

case class Person(name: String, age: String)

// this case class allows special sequence trait in FriendArray class
// this will allow you to use .map .filter etc on FriendArray
case class FriendArray(items: Array[Person]) extends IndexedSeq[Person] {
    def apply(index: Int) = items(index)
    def length = items.length
}

object FriendsProtocol extends DefaultJsonProtocol {
  implicit val personFormat = jsonFormat2(Person)
  implicit object friendListJsonFormat extends RootJsonFormat[FriendArray] {
    def read(value: JsValue) = FriendArray(value.convertTo[Array[Person]])
    def write(f: FriendArray) = ???
  } 
}

import FriendsProtocol._

val input = jsonString.parseJson
val friends = input.convertTo[FriendArray]
friends.map(x => println(x.name))
println(friends.length)

This will then print:

John
Tom
2
Aeolotropic answered 6/4, 2016 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.