1

An Api request returns a Json string like below. I've been trying to parse in Scala, add the "name" field to a list and then filter this list using another list. The API is from a third party so the format cannot be modified.

Example list (2 entries in values here but there can be up to 300):

{
    "size": 20,
    "values": [{
            "name": "mullock",
            "upstatus": "Green",
            "details": {
                "key": "rupture farms",
                "server": "mudos",
                "owner": "magog_cartel",
                "type": "NORMAL",
                "links": {
                    "self": [{
                        "address": "https://mudos.com:port/access"
                    }]
                }
            }
        },
        {
            "name": "tassadar",
            "upstatus": "Orange",
            "details": {
                "key": "archon",
                "server": "protoss",
                "owner": "aspp67",
                "type": "NORMAL",
                "links": {
                    "self": [{
                        "address": "https://aiur.com:port/access"
                    }]
                }
            }
        }
    ],
    "limit": 100
}

I've attempted to unmarshal the string using jackson and some suggested functions (below) which are used in other parts of the application but I don't fully understand this.

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper

object Json {
    /* Json/Scala translation utilities
     */
    val mapper = {
        val _mapper = new ObjectMapper() with ScalaObjectMapper
        _mapper.registerModule(DefaultScalaModule)
        _mapper
    }

    def dump(obj: Any): String = {
        mapper.writeValueAsString(obj)
    }

    def dumpObj(fields: (Any, Any)*): String = {
        dump(fields.toMap)
    }

    def read[T: Manifest](content: String): T = {
        mapper.readValue(content, mapper.constructType[T])
    }

    def readObj(content: String): Map[Any, Any] = {
        return read[Map[Any, Any]](content)
    }

}
  1. How can I access nested elements of in object Map(Any, Any)?
  2. Is this the correct way to deserialize a JSON String?

Any help greatly appreciated!

3
  • At this moment this question is dangerously close to "help me debug my code" and also "recommend a tool" kind of questions - and both are considered offtopic at StackOverflow (and usually closed). Having said that, you might want to take a look at either circe, spray or play-json Commented Apr 11, 2018 at 15:32
  • I'm asking how to access a nested element in array type ANY or how to correctly flatten a nested mixed type array from an input Json string Commented Apr 11, 2018 at 15:51
  • Please update the answer than. But the idea is that you can't - Any can really be any, including string, int, float, boolean or custom object. You need to deseralize it to some particular class in order to access nested structures. All three libs I've outlined in my comment allow that. Commented Apr 11, 2018 at 15:57

1 Answer 1

4

In Scala you should always prefer useful types over Any. Do not parse JSON into Map[String, Any] - design case classes around the Api Result data structure and read the JSON into that class:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper

case class ApiResult(size: Int, limit: Int, values: Seq[Entity])
case class Entity(name: String, upstatus: String, details: EntityDetails)
case class EntityDetails(key: String, server: String, owner: String, `type`: String, links: EntityLinks)
case class EntityLinks(self: Seq[EntityLinkAddress])
case class EntityLinkAddress(address: String)

object Foo {
  val mapper = new ObjectMapper() with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)

  def test(): Unit = {
    val json: String =
      """
        |{
        |    "size": 20,
        |    "values": [{
        |            "name": "mullock",
        |            "upstatus": "Green",
        |            "details": {
        |                "key": "rupture farms",
        |                "server": "mudos",
        |                "owner": "magog_cartel",
        |                "type": "NORMAL",
        |                "links": {
        |                    "self": [{
        |                        "address": "https://mudos.com:port/access"
        |                    }]
        |                }
        |            }
        |        },
        |        {
        |            "name": "tassadar",
        |            "upstatus": "Orange",
        |            "details": {
        |                "key": "archon",
        |                "server": "protoss",
        |                "owner": "aspp67",
        |                "type": "NORMAL",
        |                "links": {
        |                    "self": [{
        |                        "address": "https://aiur.com:port/access"
        |                    }]
        |                }
        |            }
        |        }
        |    ],
        |    "limit": 100
        |}
        |""".stripMargin

    val r = mapper.readValue[ApiResult](json)

    println(r.values.find(_.name == "tassadar"))
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

So there's no way of generally defining a deserialize method to unpack a JSon string, I have to explicitly define the structure? Also why use a case class here to define the structure instead of a regular class?
You can use lenses to work with JSON strings, but as programmers, we really prefer to work with known well-structured data, don't we? This is far easier to read, write, and to think about. If some parts of the JSON structure are unknown/optional, you can set them as Option[T] in your case class(es). You should always use case classes if possible! There are many reasons for this beyond the scope of this comment. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.