feature/dynamic_resourceDoc: add unit test for deserialize dynamic case class, and fix some bugs.

This commit is contained in:
shuang 2021-02-15 20:39:42 +08:00
parent eb9793d03f
commit 04eb93b48d
4 changed files with 112 additions and 35 deletions

View 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)
}
}

View File

@ -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"

View File

@ -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:_*)
}
}
/**

View File

@ -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)