Skip to content
rktoomey edited this page Apr 23, 2012 · 10 revisions

Do you need a custom Salat context?

Probably not! Usually, importing Salat's own global context will do the trick:

import com.novus.salat.global._

The purpose of a custom context is to globally override some default in Salat Context to suit your own use case:

  • register global key overrides
  • register per-class key overrides
  • customise type hints
  • add addition classloaders or replace the default classloader
  • customise enum serialization
  • customise BigDecimal precision with your own java.math.MathContext

How to use your own custom context

  • First, define a custom context as implicit val ctx = new Context { /* custom behaviour */ } in a package object
  • Stop importing com.novus.salat.global._
  • Import your own custom context everywhere instead.

Please be careful: accidentally mixing implicit contexts can probably will cause severe tire damage.

In the example below, use your custom context by replacing import com.novus.salat.global._ with import foo.custom_context._ everywhere you need to supply an implicit ctx val:

  package foo

  import com.novus.salat._

  package object custom_context {

    implicit val ctx = new Context {
        val name = "Custom Context"

        // some overrides or custom behavior
    }
  }

Global type hint customization

See type hinting for more information before you change anything.

Defining a context that always uses type hints, but changes the type hint field name from _typeHint to _t :

  package com.novus.salat.test

  import com.novus.salat._
  import com.mongodb.casbah.Imports._

  package object custom_type_hint {

  val CustomTypeHint = "_t"

  implicit val ctx = new Context {
      val name = "TestContext-Always"
      override val typeHintStrategy = TypeHintStrategy(when = TypeHintFrequency.Always, typeHint = CustomTypeHint)
    }
  }

Then, in your code, import your own custom context instead of Salat's global context:

  case class Alpha(x: String)

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

  scala> val a = Alpha(x = "Hello world")
  a: com.novus.salat.test.model.Alpha = Alpha(Hello world)

  scala> val dbo = grater[Alpha].asDBObject(a)
  dbo: com.mongodb.casbah.Imports.DBObject = { "_t" :  "com.novus.salat.test.model.Alpha" , "x" : "Hello world"}

  scala> val a_* = grater[Alpha].asObject(dbo)
  a_*: com.novus.salat.test.model.Alpha = Alpha(Hello world)

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

Global key remapping

Let's say that you don't want to keep repeating yourself:

@Key("_id") id: ObjectId = new ObjectId

In that case, you can define a global key override in a custom context - the example below will remap id to _id :

  package com.novus.salat.test

  import com.novus.salat._
  import com.mongodb.casbah.Imports._

  package object global_key_overrides {

    implicit val ctx = new Context {
        val name = "TestContext-GlobalKeyOverrides"
    }
    // now remap away to your heart's content
    ctx.registerGlobalKeyOverride(remapThis = "id", toThisInstead = "_id")
  }

Then, in your code, import your own custom context instead of Salat's global context:

  case class Beta(id: ObjectId = new ObjectId, x: String)

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

  scala> val b = Beta(x = "Hello world")
  b: com.novus.salat.test.model.Beta = Beta(4dac7fe775e101ed63792313,Hello world)

  scala> val dbo = grater[Beta].asDBObject(b)
  dbo: com.mongodb.casbah.Imports.DBObject = { "_typeHint" :  "com.novus.salat.test.model.Beta" ,
  "_id" : { "$oid" : "4dac7fe775e101ed63792313"},
  "x" : "Hello world"}

  scala> val b_* = grater[Beta].asObject(dbo)
  b_*: com.novus.salat.test.model.Beta = Beta(4dac7fe775e101ed63792313,Hello world)

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

Per-class key remapping

If you don't want to globally remap all instances of a field name, you can limit the key remapping per-class.

For example, given these model classes:

  import com.novus.salat.test.model

  case class Rhoda(consumed: Option[String] = None)
  case class Rhoda3(consumed: Option[String] = None)

This custom context will remap consumed for Rhoda but not for Rhoda3:

  package com.novus.salat.test

  import com.novus.salat._
  import com.novus.salat.test.model.Rhoda

  package object per_class_key_remapping {
    implicit val ctx = new Context {
      val name = "TestContext-PerClassKeyRemapping"
    }
    ctx.registerPerClassKeyOverride(classOf[Rhoda], remapThis = "consumed", toThisInstead = "fire")
  }

When serializing and deserializing instances of Rhoda, consumed will be remapped to fire. However, when serializing and deserializing instances of Rhoda3, consumed will not be remapped because the Context limits the key remapping by class.

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

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

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

  scala> import com.mongodb.casbah.Imports._
  import com.mongodb.casbah.Imports._

  scala> val rhoda = Rhoda(consumed = Some("indeed"))
  rhoda: com.novus.salat.test.model.Rhoda = Rhoda(Some(indeed))

  scala> val dbo: MongoDBObject = grater[Rhoda].asDBObject(rhoda)
  44 [run-main] INFO com.novus.salat.test.per_class_key_remapping.package$$anon$6 - perClassKeyOverrides: context=Some(TestContext-PerClassKeyRemapping) will remap key='consumed' to 'fire' for all instance of clazz='com.novus.salat.test.model.Rhoda'
  dbo: com.mongodb.casbah.Imports.MongoDBObject = Map((_typeHint,com.novus.salat.test.model.Rhoda), (fire,indeed))

  scala> grater[Rhoda].asObject(dbo)
  res1: com.novus.salat.test.model.Rhoda = Rhoda(Some(indeed))

  scala> val rhoda3 = Rhoda3(consumed = Some("indeed"))
  rhoda3: com.novus.salat.test.model.Rhoda3 = Rhoda3(Some(indeed))

  scala> val dbo2: MongoDBObject = grater[Rhoda3].asDBObject(rhoda3)
  dbo2: com.mongodb.casbah.Imports.MongoDBObject = Map((_typeHint,com.novus.salat.test.model.Rhoda3), (consumed,indeed))

  scala> grater[Rhoda3].asObject(dbo2)
  res2: com.novus.salat.test.model.Rhoda3 = Rhoda3(Some(indeed))

Classloader handling

You can register multiple classloaders in addition to the default classloader:

  package foo

  import com.novus.salat._

  package object custom_classloader  {

    implicit val ctx = new Context {
        val name = "Custom_Classloader"
    }
    ctx.registerClassLoader(MyCustomClassloader)
  }

Or replace the default classloader by overriding the classLoaders param in your own class:

  package foo

  import com.novus.salat._

  package object custom_classloader  {

    implicit val ctx = new Context {
        val name = "Custom_Classloader"
        classLoaders = Seq(MyCustomClassloader)
    }
  }

Global enum handling

Salat's default enum handling stategy in Context is by value.

  // sets up a default enum strategy of using toString to serialize/deserialize enums
  val defaultEnumStrategy = EnumStrategy.BY_VALUE

Assuming you need to change it, create a custom context like this:

  package com.novus.salat.test

  import com.novus.salat._
  import com.novus.salat.annotations.raw._

  package object handle_my_enums_diffrent {

    implicit val ctx = new Context {
      val name = "CustomEnumStrategy"
      override val defaultEnumStrategy = EnumStrategy.BY_ID
    }
  }

Global BigDecimal precision

The default precision for BigDecimal in Context is

val mathCtx = new MathContext(17, RoundingMode.HALF_UP)

Assuming you need to change it, create a custom context like this:

  package com.novus.salat.test

  import com.novus.salat._
  import java.math.{RoundingMode, MathContext}

  package object moar_precision {

    implicit val ctx = new Context {
      val name = "MorePreciseContext"
      override val mathCtx = new MathContext(17, RoundingMode.HALF_UP)
    }
  }

Last words

Careful with that axe, Eugene. You need to make sure you are using your custom context instead of the global context everywhere.

Clone this wiki locally