mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 17:56:46 +00:00
feature/dynamic_resourceDoc: add unit test for deserialize dynamic case class, and fix some bugs.
This commit is contained in:
parent
eb9793d03f
commit
04eb93b48d
68
obp-api/src/test/scala/code/util/DynamicUtilsTest.scala
Normal file
68
obp-api/src/test/scala/code/util/DynamicUtilsTest.scala
Normal file
@ -0,0 +1,68 @@
|
||||
package code.util
|
||||
|
||||
import code.api.util.DynamicUtil
|
||||
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.json
|
||||
import org.scalatest.{FlatSpec, Matchers, Tag}
|
||||
|
||||
class DynamicUtilsTest extends FlatSpec with Matchers {
|
||||
object DynamicUtilsTag extends Tag("DynamicUtils")
|
||||
implicit val formats = code.api.util.CustomJsonFormats.formats
|
||||
val zson = {
|
||||
"""
|
||||
|{
|
||||
| "name": "Sam",
|
||||
| "age": [12],
|
||||
| "isMarried": true,
|
||||
| "weight": 12.11,
|
||||
| "class": "2",
|
||||
| "def": 12,
|
||||
| "email": ["abc@def.com", "hijk@abc.com"],
|
||||
| "address": [{
|
||||
| "name": "jieji",
|
||||
| "code": 123123,
|
||||
| "street":{"road": "gongbin", "number": 123},
|
||||
| "_optional_fields_": ["code"]
|
||||
| }],
|
||||
| "street": {"name": "hongqi", "width": 12.11},
|
||||
| "_optional_fields_": ["age", "weight", "address"]
|
||||
|}
|
||||
|""".stripMargin
|
||||
}
|
||||
val zson2 = """{"road": "gongbin", "number": 123}"""
|
||||
|
||||
def buildFunction(jsonStr: String): String => Any = {
|
||||
val caseClasses = JsonUtils.toCaseClasses(json.parse(jsonStr))
|
||||
|
||||
val code =
|
||||
s"""
|
||||
| $caseClasses
|
||||
|
|
||||
| // throws exception: net.liftweb.json.MappingException:
|
||||
| //No usable value for name
|
||||
| //Did not find value which can be converted into java.lang.String
|
||||
|
|
||||
|implicit val formats = code.api.util.CustomJsonFormats.formats
|
||||
|(str: String) => {
|
||||
| net.liftweb.json.parse(str).extract[RootJsonClass]
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
val fun: Box[String => Any] = DynamicUtil.compileScalaCode(code)
|
||||
fun.orNull
|
||||
}
|
||||
|
||||
"Parse json to dynamic case object" should "success" taggedAs DynamicUtilsTag in {
|
||||
|
||||
val func = buildFunction(zson)
|
||||
val func2 = buildFunction(zson2)
|
||||
val value1 = func.apply(zson)
|
||||
val value2 = func2.apply(zson2)
|
||||
|
||||
ReflectUtils.getNestedField(value1.asInstanceOf[AnyRef], "street", "name") should be ("hongqi")
|
||||
ReflectUtils.getField(value1.asInstanceOf[AnyRef], "weight") shouldEqual Some(12.11)
|
||||
ReflectUtils.getField(value2.asInstanceOf[AnyRef], "number") shouldEqual (123)
|
||||
|
||||
}
|
||||
}
|
||||
@ -672,30 +672,24 @@ object JsonUtils {
|
||||
|
||||
private val optionalFieldName = "_optional_fields_"
|
||||
|
||||
// if Option[Boolean], Option[Double], Option[Long] will lost type argument when do deserialize with lift-json: Option[Object]
|
||||
// this will make generated scala code can't extract json to this object, So use java.lang.Xxx for these type in Option.
|
||||
private def toScalaTypeName(jValue: JValue, isOptional: Boolean = false) = jValue match {
|
||||
case _: JBool => if(isOptional) "Option[Boolean]" else "Boolean"
|
||||
case _: JString => if(isOptional) "Option[String]" else "String"
|
||||
case _: JDouble => if(isOptional) "Option[BigDecimal]" else "BigDecimal"
|
||||
case _: JInt => if(isOptional) "Option[Long]" else "Long"
|
||||
case _: JObject => if(isOptional) "Option[AnyRef]" else "AnyRef"
|
||||
case _: JArray => if(isOptional) "Option[List[Any]]" else "List[Any]"
|
||||
case _: JBool if isOptional => "Option[java.lang.Boolean]"
|
||||
case _: JBool => "Boolean"
|
||||
case _: JDouble if isOptional => "Option[java.lang.Double]"
|
||||
case _: JDouble => "Double"
|
||||
case _: JInt if isOptional => "Option[java.lang.Long]"
|
||||
case _: JInt => "Long"
|
||||
case _: JString if isOptional => "Option[String]"
|
||||
case _: JString => "String"
|
||||
case _: JObject if isOptional => "Option[AnyRef]"
|
||||
case _: JObject => "AnyRef"
|
||||
case _: JArray if isOptional => "Option[List[Any]]"
|
||||
case _: JArray => "List[Any]"
|
||||
case null | JNull | JNothing => throw new IllegalArgumentException(s"Json value must not be null")
|
||||
}
|
||||
|
||||
private def getJArrayItemTypeName(values: List[JValue], fieldName: String): String = {
|
||||
values match {
|
||||
case JBool(_) :: _ => "List[Boolean]"
|
||||
case JString(_) :: _ => "List[String]"
|
||||
case JDouble(_) :: _ => "List[BigDecimal]"
|
||||
case JInt(_) :: _ => "List[Long]"
|
||||
case JObject(_) :: _ => s"List[${fieldName.capitalize}JsonClass]"
|
||||
case JArray(arr) :: _ =>
|
||||
val itemType = getJArrayItemTypeName(arr, fieldName)
|
||||
s"List[List[$itemType]]"
|
||||
case (null | JNull | JNothing) :: _ => throw new IllegalArgumentException(s"Fields $fieldName have null item, items should not be null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* validate any nested array type field have the same structure, and not empty, and have not null item
|
||||
* @param void
|
||||
@ -775,7 +769,7 @@ object JsonUtils {
|
||||
jvalue match {
|
||||
case _: JBool => "type RootJsonClass = Boolean"
|
||||
case _: JString => "type RootJsonClass = String"
|
||||
case _: JDouble => "type RootJsonClass = BigDecimal"
|
||||
case _: JDouble => "type RootJsonClass = Double"
|
||||
case _: JInt => "type RootJsonClass = Long"
|
||||
case jObject: JObject =>
|
||||
validateJArray(jObject)
|
||||
@ -788,7 +782,7 @@ object JsonUtils {
|
||||
def buildArrayType(jArray: JArray):String = jArray.arr.head match {
|
||||
case _: JBool => "List[Boolean]"
|
||||
case _: JString => "List[String]"
|
||||
case _: JDouble => "List[BigDecimal]"
|
||||
case _: JDouble => "List[Double]"
|
||||
case _: JInt => "List[Long]"
|
||||
case v: JObject =>
|
||||
val itemsType = jObjectToCaseClass(v, "RootItem")
|
||||
@ -839,21 +833,20 @@ object JsonUtils {
|
||||
s"$escapedFieldsName: $fieldType"
|
||||
|
||||
case JField(name, arr: JArray) if name != optionalFieldName =>
|
||||
|
||||
val isOption: Boolean = optionalFields.contains(name)
|
||||
def buildArrayType(jArray: JArray): String = jArray.arr.head match {
|
||||
case _: JBool => "List[Boolean]"
|
||||
case _: JBool => "List[java.lang.Boolean]"
|
||||
case _: JDouble => "List[java.lang.Double]"
|
||||
case _: JInt => "List[java.lang.Long]"
|
||||
case _: JString => "List[String]"
|
||||
case _: JDouble => "List[BigDecimal]"
|
||||
case _: JInt => "List[Long]"
|
||||
case _: JObject => s"List[${toCaseClassName(name)}]"
|
||||
case _: JObject => s"List[${toCaseClassName(name)}]"
|
||||
|
||||
case v: JArray =>
|
||||
val nestedItmType = buildArrayType(v)
|
||||
s"List[$nestedItmType]"
|
||||
}
|
||||
|
||||
val fieldType = if (optionalFields.contains(name)) s"Option[${buildArrayType(arr)}]"
|
||||
else buildArrayType(arr)
|
||||
val fieldType = if (isOption) s"Option[${buildArrayType(arr)}]" else buildArrayType(arr)
|
||||
|
||||
val escapedFieldsName = reservedToEscaped.getOrElse(name, name)
|
||||
s"$escapedFieldsName: $fieldType"
|
||||
|
||||
@ -113,6 +113,20 @@ object ReflectUtils {
|
||||
* @return the field value of obj
|
||||
*/
|
||||
def getField(obj: AnyRef, fieldName: String): Any = operateField[Any](obj, fieldName)(Functions.doNothingFn)
|
||||
/**
|
||||
* get given object nested field value
|
||||
* @param obj
|
||||
* @param fieldName field name
|
||||
* @return the field value of obj
|
||||
*/
|
||||
def getNestedField(obj: AnyRef, rootField: String, nestedFields: String*): Any = {
|
||||
nestedFields.foldLeft(getField(obj, rootField)) { (parentObject, field) =>
|
||||
assert(parentObject != null, s"Can't read `$field` value from null.")
|
||||
assert(parentObject.isInstanceOf[AnyRef], s"Value $parentObject must be AnyRef type.")
|
||||
|
||||
getField(parentObject.asInstanceOf[AnyRef], field)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* according object name get corresponding field value
|
||||
@ -618,12 +632,14 @@ object ReflectUtils {
|
||||
tp.typeSymbol.isClass && !tp.typeSymbol.asClass.isTrait match {
|
||||
case false => Map.empty[String, ru.Type]
|
||||
case true => {
|
||||
getPrimaryConstructor(tp)
|
||||
import scala.collection.immutable.ListMap
|
||||
val paramNameToTypeList = getPrimaryConstructor(tp)
|
||||
.paramLists
|
||||
.headOption
|
||||
.getOrElse(Nil)
|
||||
.map(it => (it.name.toString, it.info))
|
||||
.toMap
|
||||
|
||||
ListMap(paramNameToTypeList:_*)
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
@ -61,7 +61,7 @@ class JsonUtilsTest extends FlatSpec with Matchers {
|
||||
| "code": 123123,
|
||||
| "street":{"road": "gongbin", "number": 123}
|
||||
| }],
|
||||
| "street": {"name": "hongqi", "width": 12.11}
|
||||
| "street": {"name": "hongqi", "width": 12.11},
|
||||
| "_optional_fields_": ["age", "weight", "address"]
|
||||
|}
|
||||
|""".stripMargin
|
||||
@ -69,8 +69,8 @@ class JsonUtilsTest extends FlatSpec with Matchers {
|
||||
val expectedCaseClass =
|
||||
"""case class AddressStreetJsonClass(road: String, number: Long)
|
||||
|case class AddressJsonClass(name: String, code: Long, street: AddressStreetJsonClass)
|
||||
|case class StreetJsonClass(name: String, width: BigDecimal)
|
||||
|case class RootJsonClass(name: String, age: Option[Long], isMarried: Boolean, weight: Option[BigDecimal], `class`: String, `def`: Long, email: List[String], address: Option[List[AddressJsonClass]], street: StreetJsonClass)""".stripMargin
|
||||
|case class StreetJsonClass(name: String, width: Double)
|
||||
|case class RootJsonClass(name: String, age: Option[java.lang.Long], isMarried: Boolean, weight: Option[java.lang.Double], `class`: String, `def`: Long, email: List[String], address: Option[List[AddressJsonClass]], street: StreetJsonClass)""".stripMargin
|
||||
|
||||
val generatedCaseClass = toCaseClass(zson)
|
||||
|
||||
@ -98,7 +98,7 @@ class JsonUtilsTest extends FlatSpec with Matchers {
|
||||
| "weight": 21.43
|
||||
| }
|
||||
|]""".stripMargin
|
||||
val expectedCaseClass = """case class RootItemJsonClass(name: String, weight: BigDecimal)
|
||||
val expectedCaseClass = """case class RootItemJsonClass(name: String, weight: Double)
|
||||
| type RootJsonClass = List[RootItemJsonClass]""".stripMargin
|
||||
|
||||
val generatedCaseClass = toCaseClass(listObjectJson)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user