Конвертируйте JSON в case-класс с вложенными объектами, используя Scala / Play - PullRequest
0 голосов
/ 09 мая 2018

Скажите, что ответ JSON, с которым я работаю, имеет следующий формат:

[
    {
       "make": "Tesla",
       "model": "Model S",
       "year": 2017,
       "color": "red",
       "owner": "Bob",
       "max_speed": 200,
       "wheel_size": 30,
       "is_convertible": true,
       "license": "ABC123",
       "cost": 50000,
       "down_payment": 2500,
       "other_property_1": 1,
       "other_property_2": 2,
       "other_property_3": 3,
       "other_property_4": 4,
       "other_property_5": 5,
       "other_property_6": 6,
       "other_property_7": 7,
       "other_property_8": 8,
       "other_property_9": 9,
       "other_property_10": 10,
       "other_property_11": 11
    }
]

JSON - это массив автомобильных объектов (всего 1 для простоты), и я пытаюсь преобразовать это в модель, используя конвертер JSON Reads. Допустим, у меня есть класс дел Car для представления каждого объекта, и у этого класса есть вложенный класс дел FinancialInfo для логического разделения количества атрибутов, чтобы избежать ограничения параметров в Scala 22.

import play.api.libs.functional.syntax._
import play.api.libs.json._

case class Car(
    make: String,
    model: String,
    year: Int,
    color: String,
    owner: String,
    maxSpeed: Int,
    wheelSize: Int,
    isConvertible: Boolean,
    license: String,
    financialInfo: FinancialInfo, // nested case class to avoid 22 param limit
    otherProperty1: Int,
    otherProperty2: Int,
    otherProperty3: Int,
    otherProperty4: Int,
    otherProperty5: Int,
    otherProperty6: Int,
    otherProperty7: Int,
    otherProperty8: Int,
    otherProperty9: Int,
    otherProperty10: Int,
    otherProperty11: Int
)

object Car {
    implicit val reads: Reads[Car] = (
        (__ \ "make").read[String] and
        (__ \ "model").read[String] and
        (__ \ "year").read[Int] and
        (__ \ "color").read[String] and
        (__ \ "owner").read[String] and
        (__ \ "max_speed").read[Int] and
        (__ \ "wheel_size").read[Int] and
        (__ \ "is_convertible").read[Boolean] and
        (__ \ "license").read[String] and
        (__ \ "financialInfo").read[FinancialInfo] and
        (__ \ "other_property_1").read[Int] and
        (__ \ "other_property_2").read[Int] and
        (__ \ "other_property_3").read[Int] and
        (__ \ "other_property_4").read[Int] and
        (__ \ "other_property_5").read[Int] and
        (__ \ "other_property_6").read[Int] and
        (__ \ "other_property_7").read[Int] and
        (__ \ "other_property_8").read[Int] and
        (__ \ "other_property_9").read[Int] and
        (__ \ "other_property_10").read[Int] and
        (__ \ "other_property_11").read[Int]
    )(Car.apply _)
}

case class FinancialInfo(
   cost: BigDecimal,
   downPayment: BigDecimal
)

object FinancialInfo {
    implicit val reads: Reads[FinancialInfo] = (
        (__ \ "cost").read[BigDecimal] and
        (__ \ "down_payment").read[BigDecimal]
    )(FinancialInfo.apply _)
}

Тем не менее, я предполагаю, что в JSON отсутствует свойство с именем financialInfo, оно неправильно анализируется. В моем реальном приложении я получаю эту ошибку при использовании response.json.validate[List[Car]]:

JsError(List(((0)/financialInfo,List(JsonValidationError(List(error.path.missing),WrappedArray()))))) 

Подводя итог, можно сказать, что в этом примере cost и down_payment не содержатся во вложенном объекте, несмотря на то, что для класса дела Car мне пришлось включить вложенную модель с именем financialInfo. Каков наилучший способ обойти эту ошибку и убедиться, что значения для cost и down_payment могут быть проанализированы? Буду признателен за любую помощь или понимание!

1 Ответ

0 голосов
/ 09 мая 2018

Reads могут быть объединены и включены друг в друга.

Итак, имея:

implicit val fiReads: Reads[FinancialInfo] = (
  (JsPath \ "cost").read[BigDecimal] and
  (JsPath \ "down_payment").read[BigDecimal]
  )(FinancialInfo.apply _)

Мы можем включить его в родительский Reads:

implicit val carReads: Reads[Car] = (
  (JsPath \ "make").read[String] and
  (JsPath \ "model").read[String] and
  fiReads  // <--- HERE!
)(Car.apply _)

Теперь со следующим JSON:

private val json =
  """
    |[
    |    {
    |       "make": "Tesla",
    |       "model": "Model S",
    |       "cost": 50000,
    |       "down_payment": 2500
    |    },
    |    {
    |       "make": "Tesla",
    |       "model": "Model D",
    |       "cost": 30000,
    |       "down_payment": 1500
    |    }
    |]
  """.stripMargin

val parsedJsValue = Json.parse(json)
val parsed = Json.fromJson[List[Car]](parsedJsValue)

println(parsed)

Проанализировано правильно:

JsSuccess(List(Car(Tesla,Model S,FinancialInfo(50000,2500)), Car(Tesla,Model D,FinancialInfo(30000,1500))),)

p.s. Reads в исходном вопросе не нужно заключать в разные object s. Связанные неявные значения будут лучше внутри той же области видимости, ближе к тому месту, где они фактически используются.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...