Skip to content

Commit

Permalink
Merge pull request #75 from eed3si9n/wip/fix-llist-roundtrip
Browse files Browse the repository at this point in the history
Fixes LListFormats
  • Loading branch information
eed3si9n committed Jul 26, 2017
2 parents 3805461 + 5b04c3f commit bdc7efa
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 50 deletions.
4 changes: 2 additions & 2 deletions core/src/main/scala/sjsonnew/CollectionFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ trait CollectionFormats {
case Some(js) =>
val size = unbuilder.beginObject(js)
val xs = (1 to size).toList map { x =>
val (k, v) = unbuilder.nextField
keyFormat.read(k) -> valueFormat.read(Some(v), unbuilder)
val (k, v) = unbuilder.nextFieldOpt
keyFormat.read(k) -> valueFormat.read(v, unbuilder)
}
unbuilder.endObject
Map(xs: _*)
Expand Down
88 changes: 44 additions & 44 deletions core/src/main/scala/sjsonnew/LList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,24 @@ trait LListFormats {
import BasicJsonProtocol._

implicit val lnilFormat: JsonFormat[LNil] = new JsonFormat[LNil] {
def write[J](x: LNil, builder: Builder[J]): Unit =
{
if (!builder.isInObject) {
builder.beginObject()
}
builder.endObject()
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): LNil =
{
if (unbuilder.isInObject) {
unbuilder.endObject()
}
LList.LNil0
}
def write[J](x: LNil, builder: Builder[J]): Unit = {
if (!builder.isInObject) builder.beginObject()
builder.endObject()
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): LNil = {
if (unbuilder.isInObject) unbuilder.endObject()
LNil
}
}

private val fieldNamesField = "$fields"
implicit def lconsFormat[A1: JsonFormat: ClassTag, A2 <: LList: JsonFormat]: JsonFormat[LCons[A1, A2]] = new JsonFormat[LCons[A1, A2]] {
val a1Format = implicitly[JsonFormat[A1]]
val a2Format = implicitly[JsonFormat[A2]]
def write[J](x: LCons[A1, A2], builder: Builder[J]): Unit =
{

implicit def lconsFormat[A1: JsonFormat: ClassTag, A2 <: LList: JsonFormat]: JsonFormat[LCons[A1, A2]] =
new JsonFormat[LCons[A1, A2]] {
val a1Format: JsonFormat[A1] = implicitly
val a2Format: JsonFormat[A2] = implicitly

def write[J](x: LCons[A1, A2], builder: Builder[J]): Unit = {
if (!builder.isInObject) {
builder.beginPreObject()
builder.addField(fieldNamesField, x.fieldNames)
Expand All @@ -90,29 +87,32 @@ trait LListFormats {
builder.addField(x.name, x.head)(a1Format)
a2Format.write(x.tail, builder)
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): LCons[A1, A2] =
jsOpt match {
case Some(js) =>
def objectPreamble(x: J) = {
unbuilder.beginPreObject(x)
val jf = implicitly[JsonFormat[Vector[String]]]
val fieldNames = unbuilder.lookupField(fieldNamesField).map(x => jf.read(Some(x), unbuilder))
unbuilder.endPreObject()
unbuilder.beginObject(x, fieldNames)
}
if (!unbuilder.isInObject) objectPreamble(js)
if (unbuilder.hasNextField) {
val (name, x) = unbuilder.nextField
if (unbuilder.isObject(x)) objectPreamble(x)
val elem = a1Format.read(Some(x), unbuilder)
val tail = a2Format.read(Some(js), unbuilder)
LCons(name, elem, tail)
}
else deserializationError(s"Unexpected end of object: $js")
case None =>
val elem = a1Format.read(None, unbuilder)
val tail = a2Format.read(None, unbuilder)
LCons("*", elem, tail)
}
}

def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): LCons[A1, A2] =
jsOpt match {
case Some(js) =>
def objectPreamble(x: J) = {
unbuilder.beginPreObject(x)
val jf = implicitly[JsonFormat[Vector[String]]]
val fieldNames = unbuilder.lookupField(fieldNamesField).map(x => jf.read(Some(x), unbuilder))
unbuilder.endPreObject()
unbuilder.beginObject(x, fieldNames)
}
if (!unbuilder.isInObject) objectPreamble(js)
if (unbuilder.hasNextField) {
val (name, optX) = unbuilder.nextFieldOpt
optX foreach { x =>
if (unbuilder.isObject(x)) objectPreamble(x)
}
val elem = a1Format.read(optX, unbuilder)
val tail = a2Format.read(Option(js), unbuilder)
LCons(name, elem, tail)
}
else deserializationError(s"Unexpected end of object: $js")
case None =>
val elem = a1Format.read(None, unbuilder)
val tail = a2Format.read(None, unbuilder)
LCons("*", elem, tail)
}
}
}
25 changes: 21 additions & 4 deletions core/src/main/scala/sjsonnew/Unbuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class Unbuilder[J](facade: Facade[J]) {
case x => stateError(x)
}

def nextField(): (String, J) =
def nextFieldOpt(): (String, Option[J]) =
state match {
case InObject =>
contexts.head match {
Expand All @@ -158,7 +158,23 @@ class Unbuilder[J](facade: Facade[J]) {
case x => stateError(x)
}

def nextFieldWithJString(): (J, J) = nextField match { case (k, v) => (facade.jstring(k), v) }
def nextFieldOptWithJString(): (J, Option[J]) =
nextFieldOpt() match {
case (k, v) => (facade.jstring(k), v)
}

@deprecated("Use nextFieldOpt that returns (String, Option[J]). nextField uses JNull to encode elided fields.", "0.8.0")
def nextField(): (String, J) =
nextFieldOpt() match {
case (k, Some(v)) => (k, v)
case (k, None) => (k, facade.jnull())
}

@deprecated("Use nextFieldOpt that returns (J, Option[J]). nextFieldOptWithJString uses JNull to encode elided fields.", "0.8.0")
def nextFieldWithJString(): (J, J) =
nextField match {
case (k, v) => (facade.jstring(k), v)
}

def lookupField(name: String): Option[J] =
state match {
Expand Down Expand Up @@ -221,9 +237,10 @@ private[sjsonnew] object UnbuilderContext {
private val size = names.size
private var idx: Int = 0
def hasNext: Boolean = idx < size
def next: (String, J) = {
def next: (String, Option[J]) = {
val name = names(idx)
val x = fields(names(idx))
// nulls and empty collections are elided, so it won't show up.
val x: Option[J] = fields.get(names(idx))
idx = idx + 1
(name, x)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ object Converter extends SupportConverter[Value] {
vectorBuilder += array.next()
}
vectorBuilder.result()
case ValueType.NIL =>
Vector()
case _ => deserializationError("Expected List as Array, but got " + value)
}
def extractObject(value: Value): (Map[String, Value], Vector[String]) =
Expand All @@ -162,6 +164,8 @@ object Converter extends SupportConverter[Value] {
mapBuilder += (keyString -> entry.getValue)
}
(mapBuilder.result(), vectorBuilder.result())
case ValueType.NIL =>
(Map.empty, Vector.empty)
case _ => deserializationError("Expected Map as MMap, but got " + value)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ object Converter extends SupportConverter[JValue] {
def extractArray(value: JValue): Vector[JValue] =
value match {
case JArray(elements) => elements.toVector
case JNull => Vector.empty
case x => deserializationError("Expected List as JArray, but got " + x)
}
def extractObject(value: JValue): (Map[String, JValue], Vector[String]) =
Expand All @@ -108,6 +109,8 @@ object Converter extends SupportConverter[JValue] {
val names = (fs map { case JField(k, v) => k }).toVector
val fields = Map((fs map { case JField(k, v) => (k, v) }): _*)
(fields, names)
case JNull =>
(Map.empty, Vector.empty)
case x => deserializationError("Expected Map as JsObject, but got " + x)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package sjsonnew
package support.scalajson.unsafe

import shaded.scalajson.ast.unsafe._

import org.scalatest.FlatSpec

import BasicJsonProtocol._

final class LListFormatSpec extends FlatSpec {
case class Foo(xs: Seq[String])

implicit val isoLList: IsoLList[Foo] = LList.isoCurried(
(a: Foo) => "xs" -> a.xs :*: LNil
) { case (_, xs) :*: LNil => Foo(xs) }

val foo = Foo(Nil)
val fooLList = "xs" -> List.empty[String] :*: LNil
val fooJson = JObject(JField("$fields", JArray(JString("xs"))))
val fooJsonStr = """{"$fields":["xs"]}"""

it should "Foo -> LList" in assert((isoLList to foo) === fooLList)
it should "Foo -> JSON" in assert(foo.toJson === fooJson)
it should "Foo -> JSON string" in assert(foo.toJsonStr === fooJsonStr)
it should "JSON string -> JSON" in assert(fooJsonStr.toJson === fooJson)
it should "JSON string -> Foo" in assert(fooJsonStr.fromJsonStr[Foo] === foo)
it should "round trip" in assertRoundTrip(foo)
it should "round trip pretty" in assertPrettyRoundTrip(foo)

private def assertRoundTrip[A: JsonWriter : JsonReader](x: A) = assert(x === x.jsonRoundTrip)
private def assertPrettyRoundTrip[A: JsonWriter : JsonReader](x: A) = assert(x === x.jsonPrettyRoundTrip)
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ object Converter extends SupportConverter[JsValue] {
def extractArray(value: JsValue): Vector[JsValue] =
value match {
case JsArray(elements) => elements
case JsNull => Vector.empty
case x => deserializationError("Expected List as JsArray, but got " + x)
}
def extractObject(value: JsValue): (Map[String, JsValue], Vector[String]) =
Expand All @@ -81,6 +82,8 @@ object Converter extends SupportConverter[JsValue] {
vectorBuilder += field._1
}
(fields, vectorBuilder.result())
case JsNull =>
(Map.empty, Vector.empty)
case x => deserializationError("Expected Map as JsObject, but got " + x)
}
}
Expand Down

0 comments on commit bdc7efa

Please sign in to comment.