From f24ceaa528213aacbfaa0318bce2fab063f3ccd1 Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Tue, 19 Jul 2011 10:47:33 +0300 Subject: [PATCH] Improve return type of 'merge' function using fundep encoding --- .../main/scala/net/liftweb/json/JsonAST.scala | 4 +- .../main/scala/net/liftweb/json/Merge.scala | 67 ++++++++++++++----- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index d3052fae64..ef15c804a3 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -29,10 +29,12 @@ object JsonAST { */ def concat(xs: JValue*) = xs.foldLeft(JNothing: JValue)(_ ++ _) + object JValue extends Merge.Mergeable + /** * Data type for Json AST. */ - sealed abstract class JValue extends Merge.Mergeable with Diff.Diffable { + sealed abstract class JValue extends Diff.Diffable { type Values /** XPath-like expression to query JSON fields by name. Matches only fields on diff --git a/core/json/src/main/scala/net/liftweb/json/Merge.scala b/core/json/src/main/scala/net/liftweb/json/Merge.scala index cea44e87e3..dcbb0ee5bd 100644 --- a/core/json/src/main/scala/net/liftweb/json/Merge.scala +++ b/core/json/src/main/scala/net/liftweb/json/Merge.scala @@ -17,6 +17,43 @@ package net.liftweb package json +/** Use fundep encoding to improve return type of merge function + * (see: http://www.chuusai.com/2011/07/16/fundeps-in-scala/) + * + * JObject merge JObject = JObject + * JArray merge JArray = JArray + * _ merge _ = JValue + */ +private [json] trait MergeDep[A <: JValue, B <: JValue, R <: JValue] { + def apply(val1: A, val2: B): R +} + +private [json] trait LowPriorityMergeDep { + implicit def jjj[A <: JValue, B <: JValue] = new MergeDep[A, B, JValue] { + def apply(val1: A, val2: B): JValue = merge(val1, val2) + + private def merge(val1: JValue, val2: JValue): JValue = (val1, val2) match { + case (JObject(xs), JObject(ys)) => JObject(Merge.mergeFields(xs, ys)) + case (JArray(xs), JArray(ys)) => JArray(Merge.mergeVals(xs, ys)) + case (JField(n1, v1), JField(n2, v2)) if n1 == n2 => JField(n1, merge(v1, v2)) + case (f1: JField, f2: JField) => f2 + case (JNothing, x) => x + case (x, JNothing) => x + case (_, y) => y + } + } +} + +private [json] trait MergeDeps extends LowPriorityMergeDep { + implicit object ooo extends MergeDep[JObject, JObject, JObject] { + def apply(val1: JObject, val2: JObject): JObject = JObject(Merge.mergeFields(val1.obj, val2.obj)) + } + + implicit object aaa extends MergeDep[JArray, JArray, JArray] { + def apply(val1: JArray, val2: JArray): JArray = JArray(Merge.mergeVals(val1.arr, val2.arr)) + } +} + /** Function to merge two JSONs. */ object Merge { @@ -27,17 +64,10 @@ object Merge { * m: JObject(List(JField(name,JString(joe)), JField(age,JInt(10)), JField(iq,JInt(105)))) * */ - def merge(val1: JValue, val2: JValue): JValue = (val1, val2) match { - case (JObject(xs), JObject(ys)) => JObject(mergeFields(xs, ys)) - case (JArray(xs), JArray(ys)) => JArray(mergeVals(xs, ys)) - case (JField(n1, v1), JField(n2, v2)) if n1 == n2 => JField(n1, merge(v1, v2)) - case (f1: JField, f2: JField) => f2 - case (JNothing, x) => x - case (x, JNothing) => x - case (_, y) => y - } + def merge[A <: JValue, B <: JValue, R <: JValue] + (val1: A, val2: B)(implicit instance: MergeDep[A, B, R]): R = instance(val1, val2) - private def mergeFields(vs1: List[JField], vs2: List[JField]): List[JField] = { + private[json] def mergeFields(vs1: List[JField], vs2: List[JField]): List[JField] = { def mergeRec(xleft: List[JField], yleft: List[JField]): List[JField] = xleft match { case Nil => yleft case JField(xn, xv) :: xs => yleft find (_.name == xn) match { @@ -50,7 +80,7 @@ object Merge { mergeRec(vs1, vs2) } - private def mergeVals(vs1: List[JValue], vs2: List[JValue]): List[JValue] = { + private[json] def mergeVals(vs1: List[JValue], vs2: List[JValue]): List[JValue] = { def mergeRec(xleft: List[JValue], yleft: List[JValue]): List[JValue] = xleft match { case Nil => yleft case x :: xs => yleft find (_ == x) match { @@ -62,10 +92,15 @@ object Merge { mergeRec(vs1, vs2) } - private[json] trait Mergeable { this: JValue => - /** Return merged JSON. - * @see net.liftweb.json.Merge#merge - */ - def merge(other: JValue): JValue = Merge.merge(this, other) + private[json] trait Mergeable extends MergeDeps { + implicit def j2m[A <: JValue](json: A): MergeSyntax[A] = new MergeSyntax(json) + + class MergeSyntax[A <: JValue](json: A) { + /** Return merged JSON. + * @see net.liftweb.json.Merge#merge + */ + def merge[B <: JValue, R <: JValue](other: B)(implicit instance: MergeDep[A, B, R]): R = + Merge.merge(json, other)(instance) + } } }