diff --git a/src/main/scala/awscala/dynamodbv2/AttributeValue.scala b/src/main/scala/awscala/dynamodbv2/AttributeValue.scala index 64d36ae3..a90c26bb 100644 --- a/src/main/scala/awscala/dynamodbv2/AttributeValue.scala +++ b/src/main/scala/awscala/dynamodbv2/AttributeValue.scala @@ -1,6 +1,7 @@ package awscala.dynamodbv2 import awscala._ +import com.amazonaws.services.dynamodbv2.model import scala.collection.JavaConverters._ import com.amazonaws.services.{ dynamodbv2 => aws } import java.util.{ Map => JMap } @@ -16,6 +17,17 @@ object AttributeValue { key -> toJavaValue(vl) } + private def recurseListValue(l: Seq[_]): Seq[aws.model.AttributeValue] = l.map { + case v: AttributeValue => + v + case v: Map[String, Any] => + new aws.model.AttributeValue().withM(recurseMapValue(v).asJava) + case v: Seq[_] => + new aws.model.AttributeValue().withL(recurseListValue(v).asJavaCollection) + case v => + toJavaValue(v) + } + def toJavaValue(v: Any): aws.model.AttributeValue = { val value = new aws.model.AttributeValue v match { @@ -28,7 +40,8 @@ object AttributeValue { case Some(s: String) => value.withSS(xs.map(_.asInstanceOf[String]).asJava) case Some(n: java.lang.Number) => value.withSS(xs.map(_.toString).asJava) case Some(s: ByteBuffer) => value.withBS(xs.map(_.asInstanceOf[ByteBuffer]).asJava) - case Some(v) => value.withSS(xs.map(_.toString).asJava) + case Some(av: AttributeValue) => value.withL(recurseListValue(xs).asJavaCollection) + case Some(v) => value.withL(recurseListValue(xs).asJavaCollection) case _ => null } case m: Map[String, Any] => value.withM(recurseMapValue(m).asJava) @@ -36,11 +49,19 @@ object AttributeValue { } } + def unpackListValue(av: java.util.List[aws.model.AttributeValue]): List[AttributeValue] = av.asScala.toList.map { + case x: aws.model.AttributeValue if x.getL == null => + AttributeValue(x) + case x: aws.model.AttributeValue if x.getL != null => + AttributeValue(l = unpackListValue(x.getL)) + } + def apply(v: aws.model.AttributeValue): AttributeValue = new AttributeValue( s = Option(v.getS), bl = Option[java.lang.Boolean](v.getBOOL).map(_.booleanValue()), n = Option(v.getN), b = Option(v.getB), + l = Option(v.getL).map(unpackListValue).getOrElse(Nil), m = Option(v.getM), ss = Option(v.getSS).map(_.asScala).getOrElse(Nil), ns = Option(v.getNS).map(_.asScala).getOrElse(Nil), @@ -53,6 +74,7 @@ case class AttributeValue( bl: Option[Boolean] = None, n: Option[String] = None, b: Option[ByteBuffer] = None, + l: Seq[AttributeValue] = Nil, m: Option[JMap[String, aws.model.AttributeValue]] = None, ss: Seq[String] = Nil, ns: Seq[String] = Nil, @@ -63,6 +85,7 @@ case class AttributeValue( bl.foreach(setBOOL(_)) setN(n.orNull[String]) setB(b.orNull[ByteBuffer]) + setL(l.map(_.asInstanceOf[aws.model.AttributeValue]).asJavaCollection) setM(m.orNull[JMap[String, aws.model.AttributeValue]]) setSS(ss.asJava) setNS(ns.asJava) diff --git a/src/test/scala/awscala/DynamoDBV2Spec.scala b/src/test/scala/awscala/DynamoDBV2Spec.scala index 975c8477..059f0546 100644 --- a/src/test/scala/awscala/DynamoDBV2Spec.scala +++ b/src/test/scala/awscala/DynamoDBV2Spec.scala @@ -4,6 +4,7 @@ import awscala.dynamodbv2._ import com.amazonaws.services.{ dynamodbv2 => aws } import org.scalatest._ import org.slf4j._ +import scala.collection.mutable.ArrayBuffer import scala.util.Try class DynamoDBV2Spec extends FlatSpec with Matchers { @@ -222,4 +223,52 @@ class DynamoDBV2Spec extends FlatSpec with Matchers { users.destroy() } + it should "encode nested list and boolean datatypes properly" in { + implicit val dynamoDB = DynamoDB.local() + + val tableName = s"Users_${System.currentTimeMillis}" + val table = Table( + name = tableName, + hashPK = "Id", + attributes = Seq( + AttributeDefinition("Id", AttributeType.Number) + ) + ) + val createdTableMeta: TableMeta = dynamoDB.createTable(table) + log.info(s"Created Table: ${createdTableMeta}") + + println(s"Waiting for DynamoDB table activation...") + var isTableActivated = false + while (!isTableActivated) { + dynamoDB.describe(createdTableMeta.table).map { meta => + isTableActivated = meta.status == aws.model.TableStatus.ACTIVE + } + Thread.sleep(1000L) + print(".") + } + println("") + println(s"Created DynamoDB table has been activated.") + + val users: Table = dynamoDB.table(tableName).get + + val stuffList = (("a" :: "b" :: Nil) :: "b" :: "c" :: Nil) + users.put(1, "Name" -> "John", "Sex" -> "Male", "Age" -> 12) + users.put(2, "Name" -> "Bob", "Sex" -> "Male", "Age" -> 14, "Friend" -> true, "Stuff" -> stuffList) + users.put(3, "Name" -> "Bob", "Sex" -> "Male", "Age" -> 14, "Friend" -> true, "Stuff" -> (Map("libpq" -> List(Map("a"->"b"))) :: "b" :: "c" :: Nil)) + + + def flatList(a:List[AttributeValue]): List[String] = a match { + case Nil => Nil + case AttributeValue(Some(s),_,_,_,_,_,_,_,_) :: tail => + s :: flatList(tail) + case AttributeValue(_,_,_,_,l,_,_,_,_) :: tail => flatList(l.toList) ::: flatList(tail) + } + + flatList(users.get(2).get.attributes.find(_.name == "Stuff").get.value.l.toList) should equal(List("a","b","b","c")) + + users.get(3).get.attributes.find(_.name == "Stuff").get.value.l.head.m.get.get("libpq").getL.get(0).getM.get("a").getS should equal("b") + + users.destroy() + } + }