Search code examples
jsonscalajacksoncase-class

Scala - how to take json as input arguments and parse it?


I am writing a small scala practice code where my input is going to be in the fashion -

{
  "code": "",
  "unique ID": "",
  "count": "",
  "names": [
    {
      "Matt": {
        "name": "Matt",
        "properties": [
          "a",
          "b",
          "c"
        ],
        "fav-colour": "red"
      },
      "jack": {
        "name": "jack",
        "properties": [
          "a",
          "b"
        ],
        "fav-colour": "blue"
      }
    }
  ]
}

I'll be passing this file as an command line argument. I want to know that how do I accept the input file parse the json and use the json keys in my code?


Solution

  • You may use a json library such as play-json to parse the json content.

    You could either operate on the json AST or you could write case classes that have the same structure as your json file and let them be parsed.

    You can find the documentation of the library here.


    You'll first have to add playjson as depedency to your project. If you're using sbt, just add to your build.sbt file:

    libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.13"
    

    Play json using AST

    Let's read the input file:

    import play.api.libs.json.Json
    
    object Main extends App {
      // first we'll need a inputstream of your json object
      // this should be familiar if you know java.
      val in = new FileInputStream(args(0))
    
      // now we'll let play-json parse it
      val json = Json.parse(in)
    }
    

    Let's extract some fields from the AST:

    val code = (json \ "code").as[String]
    val uniqueID = (json \ "unique ID").as[UUID]
    
    for {
      JsObject(nameMap) ← (json \ "names").as[Seq[JsObject]]
      (name, userMeta) ← nameMap // nameMap is a Map[String, JsValue]
    } println(s"User $name has the favorite color ${(userMeta \ "fav-colour").as[String]}")
    

    Using Deserialization

    As I've just described, we may create case classes that represent your structure:

    case class InputFile(code: String, `unique ID`: UUID, count: String, names: Seq[Map[String, UserData]])
    
    case class UserData(name: String, properties: Seq[String], `fav-colour`: String)
    

    In addition you'll need to define an implicit Format e.g. in the companion object of each case class. Instead of writing it by hand you can use the Json.format macro that derives it for you:

    object UserData {
      implicit val format: OFormat[UserData] = Json.format[UserData]
    }
    
    object InputFile {
      implicit val format: OFormat[InputFile] = Json.format[InputFile]
    }
    

    You can now deserialize your json object:

    val argumentData = json.as[InputFile]
    

    I generally prefer this approach but in your case the json structure does not fit really well. One improvement could be to add an additional getter to your InputFile class that makes accesing the fields with space and similar in the name easier:

    case class InputFile(code: String, `unique ID`: UUID, count: String, names: Seq[Map[String, String]]) {
      // this method is nicer to use
      def uniqueId = `unique ID`
    }