Skip to content
This repository

JSON

Introduced with 0.0.8-SNAPSHOT, Salat's JSON support leverages json4s to cleanly move between JSON and your model objects.

Goals

Simplicity. Flexibility. Consistency.

  • provide JSON serialization that is consistent with DBObject behaviour while being customizable for JSON-specific concerns
  • avoid model contortions like Option parameters that contain boolean values, maps, or collections
  • avoid "double entry" annotations, where your model is annotated once for Salat and once for another JSON library

Supports

  • JSON object <-> model object
  • JSON array of objects <-> list of model objects
  • default arguments
  • Salat annotations - @Key, @Ignore, @Persist, @Salat
  • customizable date strategy
  • customizable ObjectId strategy

Example

Create some model objects in model.scala:

package model

import com.novus.salat.annotations._

import org.joda.time.DateTime
import org.bson.types.ObjectId
import com.novus.salat.annotations._

case class Amsterdam(@Key("_id") id: ObjectId,
                     a: String,
                     b: Int,
                     c: Double,
                     d: Boolean,
                     e: DateTime)

@Salat
trait Baltimore {
  @Key("_id") def id: ObjectId  // when you use @Key in a superclass, you don't have to repeat yourself!
  def s: String
}
case class Casablanca(id: ObjectId, s: String) extends Baltimore
case class Denmark(id: ObjectId, s: String, d: Double) extends Baltimore

case class Edison(d: DateTime)

Create a custom context in context.scala:

package myApp

import com.novus.salat.{TypeHintFrequency, StringTypeHintStrategy, Context}
import com.novus.salat.json.{StringDateStrategy, JSONConfig}
import org.joda.time.format.ISODateTimeFormat
import org.joda.time.DateTimeZone

package object context {
  implicit val ctx = new Context {
      val name = "json-test-context"
      override val typeHintStrategy = StringTypeHintStrategy(when = TypeHintFrequency.WhenNecessary,
        typeHint = "_t")
      override val jsonConfig = JSONConfig(dateStrategy =
        StringDateStrategy(dateFormatter = ISODateTimeFormat.dateTime.withZone("US/Eastern")))
    }
}

Use the following setup in sbt console:

scala> import com.novus.salat._
import com.novus.salat._

scala> import model._
import model._

scala> import myApp.context._
import myApp.context._

scala> import org.scala_tools.time.Imports._
import org.scala_tools.time.Imports._

scala> val id = new org.bson.types.ObjectId
id: org.bson.types.ObjectId = 4fdf3bc2c89cd58b22f27811

scala> val s = "Hello"
s: java.lang.String = Hello

scala> val i = 99 // problems but <-> JSON is no longer one
i: Int = 99

scala> val d = 3.14
d: Double = 3.14

scala> val b = false
b: Boolean = false

scala> val dt =  new DateTime(2011, 12, 28, 14, 37, 56, 8, DateTimeZone.forID("US/Eastern"))
dt: org.joda.time.DateTime = 2011-12-28T14:37:56.008-05:00

scala> val a = Amsterdam(id = id, s = s, i = i, d = d, b = b, dt = dt)
a: model.Amsterdam = Amsterdam(4fdf3bc2c89cd58b22f27811,Hello,99,3.14,false,2011-12-28T14:37:56.008-05:00)

scala> val b = List(Casablanca(id = new ObjectId, s = "c"), Denmark(id = new ObjectId, s = "d", d = 1.618))
b: List[Product with model.Baltimore] = List(Casablanca(4fe5f9ef4cea1c9d3216fe5a,c), Denmark(4fe5f9ef4cea1c9d3216fe5b,d,1.618))

To JSON

JObject

scala> grater[Amsterdam].toJSON(a)
res0: org.json4s.JsonAST.JObject = JObject(List(JField(_id,JObject(List(JField($oid,JString(4fdf3bc2c89cd58b22f27811))))), JField(s,JString(Hello)), JField(i,JInt(99)), JField(d,JDouble(3.14)), JField(b,JBool(false)), JField(dt,JString(2011-12-28T19:37:56.008Z))))

JArray

scala> grater[Baltimore].toJSONArray(b)
res1: org.json4s.JsonAST.JArray = JArray(List(JObject(List(JField(_t,JString(model.Casablanca)), JField(_id,JString(4fe5f9ef4cea1c9d3216fe5a)), JField(s,JString(c)))), JObject(List(JField(_t,JString(model.Denmark)), JField(_id,JString(4fe5f9ef4cea1c9d3216fe5b)), JField(s,JString(d)), JField(d,JDouble(1.618))))))

Pretty JSON

scala> grater[Amsterdam].toPrettyJSON(a)
res1: String =
{
  "_id":{
    "$oid":"4fdf3bc2c89cd58b22f27811"
  },
  "s":"Hello",
  "i":99,
  "d":3.14,
  "b":false,
  "dt":"2011-12-28T19:37:56.008Z"
}

scala> grater[Baltimore].toPrettyJSONArray(b)
res3: String =
[{
  "_t":"model.Casablanca",
  "_id":"4fe5f9ef4cea1c9d3216fe5a",
  "s":"c"
},{
  "_t":"model.Denmark",
  "_id":"4fe5f9ef4cea1c9d3216fe5b",
  "s":"d",
  "d":1.618
}]

Compact JSON

scala> grater[Amsterdam].toCompactJSON(a)
res3: String = {"_id":{"$oid":"4fdf3bc2c89cd58b22f27811"},"s":"Hello","i":99,"d":3.14,"b":false,"dt":"2011-12-28T19:37:56.008Z"}

scala> grater[Baltimore].toCompactJSONArray(b)
res2: String = [{"_t":"model.Casablanca","_id":"4fe5f9ef4cea1c9d3216fe5a","s":"c"},{"_t":"model.Denmark","_id":"4fe5f9ef4cea1c9d3216fe5b","s":"d","d":1.618}]

From JSON

Supports:

  • from JObject or string representing a JSON object to model object
  • from JArray or string representing an array of JSON objects to list of model objects
scala> val j = grater[Amsterdam].toCompactJSON(a)
j: String = {"_id":{"$oid":"4fdf3bc2c89cd58b22f27811"},"s":"Hello","i":99,"d":3.14,"b":false,"dt":"2011-12-28T14:37:56.008-05:00"}

scala> val a_* = grater[Amsterdam].fromJSON(j)
a_*: model.Amsterdam = Amsterdam(4fdf3bc2c89cd58b22f27811,Hello,99,3.14,false,2011-12-28T14:37:56.008-05:00)

scala> a_* == a
res0: Boolean = true

scala> val arr = grater[Baltimore].toJSONArray(b)
arr: org.json4s.JsonAST.JArray = JArray(List(JObject(List(JField(_t,JString(model.Casablanca)), JField(_id,JString(4fe5f9ef4cea1c9d3216fe5a)), JField(s,JString(c)))), JObject(List(JField(_t,JString(model.Denmark)), JField(_id,JString(4fe5f9ef4cea1c9d3216fe5b)), JField(s,JString(d)), JField(d,JDouble(1.618))))))

scala> val b_* = grater[Baltimore].fromJSONArray(arr)
b_*: List[model.Baltimore] = List(Casablanca(4fe5f9ef4cea1c9d3216fe5a,c), Denmark(4fe5f9ef4cea1c9d3216fe5b,d,1.618))

scala> b_* == b
res4: Boolean = true

Configuring JSON support in the context

JSON specific configuration is provided by com.novus.salat.json.JSONConfig.

Currently you can configure:

  • type hinting
  • date strategy
  • ObjectId strategy
  • ability to output null values

Creating a custom context

Import myApp.context._. Don't import com.novus.salat.global._.

package myApp

import com.novus.salat.{ TypeHintFrequency, StringTypeHintStrategy, Context }
import com.novus.salat.json._
import org.joda.time.format.ISODateTimeFormat
import org.joda.time.DateTimeZone

package object context {
  implicit val ctx = new Context {
    val name = "json-test-context"
    override val typeHintStrategy = StringTypeHintStrategy(when = TypeHintFrequency.WhenNecessary,
      typeHint = "_t")
    override val jsonConfig = JSONConfig(
      dateStrategy = StringDateStrategy(dateFormatter = ISODateTimeFormat.dateTime.withZone(DateTimeZone.forID("US/Eastern"))),
      objectIdStrategy = StringObjectIdStrategy)
  }
}

Type hinting

One question: will you ever need to deserialize JSON output into model objects?

Yes

If the answer is yes, use the "when necessary" type hinting strategy with a short type hint key:

package myApp

import com.novus.salat.{TypeHintFrequency, StringTypeHintStrategy, Context}

package object when_necessary_context {
  implicit val ctx = new Context {
      val name = "json-context"
      override val typeHintStrategy = StringTypeHintStrategy(when = TypeHintFrequency.WhenNecessary,
        typeHint = "_t")
    }
}

No

package myApp

import com.novus.salat.{NeverTypeHint, Context}

package object never_typehint_context {
  implicit val ctx = new Context {
      val name = "json-context"
      override val typeHintStrategy = NeverTypeHint
    }
}

ObjectId Support

Salat supplies the following options for handling ObjectId in JSON.

scala> val d = Denmark(id = new ObjectId("4fe5f9ef4cea1c9d3216fe5b"), s = "d", d = 1.618)
d: model.Denmark = Denmark(4fe5f9ef4cea1c9d3216fe5b,d,1.618)

Strict JSON

StrictJSONObjectIdStrategy serializes your ObjectId as a JSON object with key $oid

scala> grater[Denmark].toPrettyJSON(d)
res0: String =
{
  "_t":"model.Denmark",
  "_id":{
    "$oid":"4fe5f9ef4cea1c9d3216fe5b"
  },
  "s":"d",
  "d":1.618
}

String

StringObjectIdStrategy serializes your ObjectId as a sring

scala> grater[Denmark].toPrettyJSON(d)
res5: String =
{
  "_t":"model.Denmark",
  "_id":"4fe5f9ef4cea1c9d3216fe5b",
  "s":"d",
  "d":1.618
}

Custom

Extend JSONObjectIdStrategy and do whatever you like.

Date Support

Time zone defaults to DateTimeZone.UTC. To use a different time zone, explicitly supply it: the examples show how to use US/Eastern instead of UTC.

scala> val dt = new DateTime(2011, 12, 28, 14, 37, 56, 8, DateTimeZone.forID("US/Eastern"))
dt: org.joda.time.DateTime = 2011-12-28T14:37:56.008-05:00

scala> val e = Edison(d = dt)
e: model.Edison = Edison(2011-12-28T14:37:56.008-05:00)

String

StringDateStrategy

  • serializes dates as strings according to a custom format
  • defaults to ISO8601 format with UTC time zone for serializing and deserializing dates
scala> grater[Edison].toPrettyJSON(e)
res0: String =
{
  "d":"2011-12-28T14:37:56.008-05:00"
}

Timestamp

TimestampDateStrategy

  • serializes dates as a unix timestamp
  • deserializes to Date or DateTime using supplied time zone
  • defaults to UTC time zone when deserializing dates
scala> grater[Edison].toPrettyJSON(e)
res0: String =
{
  "d":1325101076008
}

Strict JSON

StrictJSONDateStrategy

  • output dates as an object $date as the key and a timestamp as the value
  • defaults to UTC time zone when deserializing dates
scala> grater[Edison].toPrettyJSON(e)
res0: String =
{
  "d":{
    "$date":1325101076008
  }
}

Custom

Implement JSONDateStrategy to produce a custom date serialization.

Something went wrong with that request. Please try again.