получение спрейсера json (scala) для отчета о том, какое подполе в объекте json вызвало ошибки десериализации - PullRequest
0 голосов
/ 26 марта 2019

Мы используем спрей json для разбора json на классы scala, и мы хотели бы помочь клиентам нашего сервиса быстро определить, какие именно атрибуты являются ошибочными, если строку json не удается преобразовать в соответствующие классы дел. У меня есть надуманный пример класса:

корпус класса Animal ( имя: строка, плотоядное животное: булево, сука: логическая)

Вот плохой JSON, для которого мы хотели бы информативное сообщение указывает на точное поле, которое является недопустимым, при ошибке разбора.

{ "Название": "медведь-Мишке-28", "плотоядное животное": "notbool", "женщина": ложь}

В простом тесте ниже я вижу следующее сообщение об исключении:

Ожидается JsBoolean, но получил "notbool"

, который говорит мне, что пошло не так, но не в каком поле.

Простой код, выдающий недостаточно конкретное сообщение об ошибке

package examples

import org.scalatest.{FreeSpec, Inspectors, Matchers}
import spray.json.DefaultJsonProtocol._
import spray.json._

class AnotherDeserializationErrorMsgTest extends FreeSpec with Matchers with Inspectors {

  case class Animal(
          name: String,
          carnivore: Boolean,
          female: Boolean
  )


 implicit private val assetMetaItem: RootJsonFormat[Animal]     = jsonFormat3(Animal.apply)

  "should provide info on which field could not be deserialized" in {
    val animalJson =
      """{
                     "name":"bear-210k",
                     "carnivore": "notbool",
                     "female": false
        }""".stripMargin

    val parsed = animalJson.parseJson
    val error = intercept[Exception] {
      val animal = parsed.convertTo[Animal]
    }
    println("exception msg : " + error.getMessage)
    println("exception cause msg : " + error.getCause.getMessage)
  }
}

Я прошел гимнастику, чтобы попытаться исправить ошибку, и мне это удалось. Пользовательский код синтаксического анализа показан ниже. Это дает мне конкретную ошибку, которую я хочу, но мой коллега, который вполне осведомлен о спрей Json У парсинга есть теория, что есть способ просто полагаться на доступные методы библиотеки спреев (без пользовательского кода) и все еще получают более конкретную ошибку, которую мы ищем (т.е. ошибка указывает на точный атрибут, вызвавший сбой)

Если вы запустите код ниже, вы увидите:

exception msg : can't deserialize. Error in Animal field: global
exception cause msg : Expected JsBoolean, but got "notbool"

Это хорошо. Это говорит нам точное проблемное поле, которое не делает более простая версия. Однако мы очень хотелось бы подход, который дает конкретные ошибки, без всего дополнительного кода для записи. Спасибо!

Сложный код, который выдает конкретное сообщение об ошибке

package examples

import org.scalatest.{FreeSpec, Inspectors, Matchers}
import spray.json.DefaultJsonProtocol._
import spray.json._

class DeserializationErrorMsgTest extends FreeSpec with Matchers with Inspectors {

  case class Animal(
          name: String,
          carnivore: Boolean,
          female: Boolean
  )

  implicit val targetJsonFormat: RootJsonFormat[Animal] = new RootJsonFormat[Animal] {

    /**
      * Enables specification of the exact field name within the Animal object that failed to deserialize. If we
      * do not convert via this method then the exception we get will look something like :
      * Expected JsBoolean, but got 'other...' (in the case where Animal.carnivore field is an invalid boolean value)
      */
    private def convertTo[T: JsonReader](value: JsValue, fieldNames: List[String]) = {
      try {
        value.convertTo[T]
      } catch {
        case e: Throwable =>
          throw new RuntimeException(s"can't deserialize. Error in Animal field: ${fieldNames.mkString(".")}", e)
      }
    }

    override def read(json: JsValue): Animal = json match {
      case JsObject(fields) =>
        val name: JsValue =
          fields.getOrElse(
            "name",
            deserializationError(msg = "missing field 'name' from Animal")
          )
        val carnivore: JsValue =
          fields.getOrElse(
            "carnivore",
            deserializationError(msg = "missing field 'carnivore' from Animal")
          )
        val female: JsValue =
          fields.getOrElse(
            "female",
            deserializationError(msg = "missing field 'female' from Animal")
          )

        Animal(
          convertTo[String](name, List("name")),
          convertTo[Boolean](carnivore, List("global")),
          convertTo[Boolean](female, List("female"))
        )
      case _ => deserializationError("Failed to deserialize Animal")
    }

    override def write(obj: Animal): JsValue = ???
  }

  "should provide info on which field could not be deserialized" in {
    val animalJson =
      """{
                     "name":"bear-210k",
                     "carnivore": "notbool",
                     "female": false
        }""".stripMargin

    val parsed = animalJson.parseJson
    val error = intercept[Exception] {
      val animal = parsed.convertTo[Animal]
    }
    println("exception msg : " + error.getMessage)
    println("exception cause msg : " + error.getCause.getMessage)
  }
}
...