Skip to content

Instantly share code, notes, and snippets.

@sadache
Last active August 29, 2015 14:02
Show Gist options
  • Select an option

  • Save sadache/d357ced8f6c942bca81a to your computer and use it in GitHub Desktop.

Select an option

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

jto commented Jun 23, 2014

Copy link
Copy Markdown

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

sadache commented Jun 23, 2014

Copy link
Copy Markdown
Author

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

jto commented Jun 23, 2014

Copy link
Copy Markdown

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

sadache commented Jun 23, 2014

Copy link
Copy Markdown
Author

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

@jto

jto commented Jun 24, 2014

Copy link
Copy Markdown

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