Skip to content

Instantly share code, notes, and snippets.

@sadache
Last active August 29, 2015 14:02
Show Gist options
  • Save sadache/d357ced8f6c942bca81a to your computer and use it in GitHub Desktop.
Save sadache/d357ced8f6c942bca81a to your computer and use it in GitHub Desktop.
Faster with much less memory consumption JSON object reader
def objectReader[T1,T2,T3,T4,R](t1: String, t2: String, t3: String, t4: String)(f: (T1,T2,T3,T4) => R)(implicit readsT1:Reads[T1], readsT2:Reads[T2], readsT3:Reads[T3], readsT4:Reads[T4]): Reads[R] = {
def orElse[A](a:A, default: =>A) = if(a!=null) a else default
Reads[R]{
case JsObject(fields) =>
var t1V:JsResult[T1] = null.asInstanceOf[JsResult[T1]]
var t2V:JsResult[T2] = null.asInstanceOf[JsResult[T2]]
var t3V:JsResult[T3] = null.asInstanceOf[JsResult[T3]]
var t4V:JsResult[T4] = null.asInstanceOf[JsResult[T4]]
var els = fields.toIterator
while((els.hasNext) && (t1V == null || t2V == null || t3V == null || t4V == null)){
val (key, value) = els.next()
key match {
case `t1` => t1V = readsT1.reads(value)
case `t2` => t2V = readsT2.reads(value)
case `t3` => t3V = readsT3.reads(value)
case `t4` => t4V = readsT4.reads(value)
case _ => //
}
}
(t1V, t2V, t3V, t4V) match {
case (JsSuccess(tt1, _), JsSuccess(tt2, _), JsSuccess(tt3, _), JsSuccess(tt4, _)) =>
JsSuccess(f(tt1, tt2, tt3, tt4))
case _ =>
List(
orElse(t1V, JsError(Seq((JsPath.\(t1), Seq(ValidationError("error.path.missing")))))),
orElse(t2V, JsError(Seq((JsPath.\(t2), Seq(ValidationError("error.path.missing")))))),
orElse(t3V, JsError(Seq((JsPath.\(t3), Seq(ValidationError("error.path.missing")))))),
orElse(t4V, JsError(Seq((JsPath.\(t4), Seq(ValidationError("error.path.missing"))))))
).collect { case e: JsError => e }.reduceLeft(_.++(_))
}
case js => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsobject"))))
}
}
@jto
Copy link

jto commented Jun 23, 2014

I suspect your saving memory because JsObject is implemented like:

case class JsObject(fields: Seq[(String, JsValue)]) extends JsValue {
  lazy val value: Map[String, JsValue] = fields.toMap
  override def \(fieldName: String): JsValue = value.get(fieldName).getOrElse(super.\(fieldName))

So as soon as you call \, the values in your Seq may be copied in a Map (depending on the impl of toMap), which would be especially bad for nested objects.

If it does copy, value.get(fieldName) is probably very time efficient. All in all, you're probably trading time for space.

@sadache
Copy link
Author

sadache commented Jun 23, 2014

Faster actually. In both cases you'd have to go through all the fields. After this one, you've got the answer, for the other you've got nothing. This one is actually much faster if a given JsValue is used once, and mostly faster if it is not used a lot of times. Since JSON is a transportation and serialisation format in Scala, it makes this algorithm a better fit.

@jto
Copy link

jto commented Jun 23, 2014

Good point. I'm not a big fan of the solution though, as you may have expected ;)

A definition of JsObject along the line of case class JsObject(fields: Map[String, JsValue]) would be much more appropriate in your case.

@sadache
Copy link
Author

sadache commented Jun 23, 2014

It used to be so, except you'd loose the original order of fields :)

@jto
Copy link

jto commented Jun 24, 2014

I know. There's no "good" solution :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment