Skip to content

jvican/dijon

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
bin
 
 
 
 
 
 
 
 
 
 
 
 

Continuous Integration

dijon - Dynamic JSON in Scala

val (name, age) = ("Tigri", 7)
val cat = json"""
  {
    "name": "$name",
    "age": $age,
    "hobbies": ["eating", "purring"],
    "is cat": true
  }
"""
assert(cat.name == name)                         // dynamic type
assert(cat.age == age)
val Some(catAge: Int) = cat.age.asInt
assert(catAge == age)
assert(cat.age.asBoolean == None)

val catMap = cat.toMap                           // view as a hashmap
assert(catMap.toMap.keysIterator.toSeq == Seq("name", "age", "hobbies", "is cat"))

assert(cat.hobbies(1) == "purring") // array access
assert(cat.hobbies(100) == None)    // missing element
assert(cat.`is cat` == true)        // keys with spaces/symbols/scala-keywords need to be escaped with ticks
assert(cat.email == None)           // missing key

val vet = `{}`                      // create empty json object
vet.name = "Dr. Kitty Specialist"   // set attributes in json object
vet.phones = `[]`                   // create empty json array
val phone = "(650) 493-4233"
vet.phones(2) = phone               // set the 3rd item in array to this phone
assert(vet.phones == mutable.Seq(None, None, phone))  // first 2 entries None

vet.address = `{}`
vet.address.name = "Animal Hospital"
vet.address.city = "Palo Alto"
vet.address.zip = 94306
assert(vet.address == mutable.Map[String, SomeJson]("name" -> "Animal Hospital", "city" -> "Palo Alto", "zip" -> 94306))

cat.vet = vet                            // set the cat.vet to be the vet json object we created above
assert(cat.vet.phones(2) == phone)
assert(cat.vet.address.zip == 94306)     // json deep access

println(cat) // {"name":"Tigri","age":7,"hobbies":["eating","purring"],"is cat":true,"vet":{"name":"Dr. Kitty Specialist","phones":[null,null,"(650) 493-4233"],"address":{"name":"Animal Hospital","city":"Palo Alto","zip":94306}}}

assert(cat == parse(cat.toString))   // round-trip test

var basicCat = cat -- "vet"                                  // remove 1 key
basicCat = basicCat -- ("hobbies", "is cat", "paws")         // remove multiple keys ("paws" is not in cat)
assert(basicCat == json"""{ "name": "Tigri", "age": 7}""")   // after dropping some keys above
  • Simple deep-merging:
val scala = json"""
{
  "name": "scala",
  "version": "2.13.2",
  "features": {
    "functional": true,
    "awesome": true
  }
}
"""

val java = json"""
{
  "name": "java",
  "features": {
    "functional": [0, 0],
    "terrible": true
  },
  "bugs": 213
}
"""

val scalaCopy = scala.deepCopy
val javaCopy = java.deepCopy

assert((scala ++ java) == json"""{"name":"java","version":"2.13.2","features":{"functional":[0,0],"terrible":true,"awesome":true},"bugs":213}""")
assert((java ++ scala) == json"""{"name":"scala","version":"2.13.2","features":{"functional": true,"terrible":true,"awesome":true},"bugs":213}""")

assert(scala == scalaCopy)       // original json objects stay untouched after merging
assert(java == javaCopy)
val json = `{}`
json.aString = "hi"                        // compiles
json.aBoolean = true                       // compiles
json.anInt = 23                            // compiles
//json.somethingElse = Option("hi")       // does not compile
val Some(i: Int) = json.anInt.asInt
assert(i == 23)
assert(json.aBoolean.asInt == None)
  • obj() and arr() constructor functions for building up complex JSON values with less overhead:
val rick = obj(
  "name" -> name,
  "age" -> age,
  "class" -> "human",
  "weight" -> 175.1,
  "is online" -> true,
  "contact" -> obj(
    "emails" -> arr(email1, email2),
    "phone" -> obj(
      "home" -> "817-xxx-xxx",
      "work" -> "650-xxx-xxx"
    )
  ),
  "hobbies" -> arr(
    "eating",
    obj(
      "games" -> obj(
        "chess" -> true,
        "football" -> false
      )
    ),
    arr("coding", arr("python", "scala")),
    None
  ),
  "toMap" -> arr(23, 345, true),
  "apply" -> 42
)

See the spec for more examples.

Also, for the dijon.codec an additional functionality is available when using jsoniter-scala-core, like:

  • parsing/serialization from/to byte arrays, byte buffers, and input/output streams
  • parsing of streamed JSON values (concatenated or delimited by whitespace characters) and JSON arrays from input streams using callbacks without the need of holding a whole input in the memory
  • use a custom configuration for parsing and serializing

See jsoniter-scala-core spec for more details and code samples.

Usage

  1. Add the following to your build.sbt:
libraryDependency += "me.vican.jorge" %% "dijon" % "0.6.0" // Use %%% instead of %% for Scala.js
  1. Turn on support of dynamic types by adding import clause:
import scala.language.dynamics._

or by setting the scala compiler option:

scalacOptions += "-language:dynamics"
  1. Add import of the package object of dijon for the main functionality:
import dijon._
  1. Optionally, add import of package object of jsoniter-scala-core for extended json functionality:
import com.github.plokhotnyuk.jsoniter_scala.core._

TODO

  • BigInt support
  • Circular references checker
  • YAML interpolator
  • Macro for type inference to induce compile-time errors where possible
  • JSON string interpolator fills in braces, quotes and commas etc