Вызовите исключение при разборе JSON с неверной схемой в поле Optional - PullRequest
2 голосов
/ 01 мая 2020

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

У меня есть следующий класс дел:

case class SimpleFeature(
  column: String,
  valueType: String,
  nullValue: String,
  func: Option[String])

case class TaskConfig(
  taskInfo: TaskInfo,
  inputType: String,
  training: Table,
  testing: Table,
  eval: Table,
  splitStrategy: SplitStrategy,
  label: Label,
  simpleFeatures: Option[List[SimpleFeature]],
  model: Model,
  evaluation: Evaluation,
  output: Output)

И это часть файла JSON, на который я хочу обратить внимание:

"simpleFeatures": [
  {
    "column": "pcat_id",
    "value": "categorical",
    "nullValue": "DUMMY"
  },
  {
    "column": "brand_code",
    "valueType": "categorical",
    "nullValue": "DUMMY"
  }
]

Как видите, первый элемент содержит ошибку в схеме, и при разборе я хочу вызвать ошибку. В то же время я хочу сохранить необязательное поведение на случай, если нет объекта для анализа.

Одна идея, которую я изучал некоторое время, - создать собственный сериализатор и вручную проверять поля, но не уверен, что я на правильном пути

object JSONSerializer extends CustomKeySerializer[SimpleFeatures](format => {
  case jsonObj: JObject => {
    case Some(simplFeatures (jsonObj \ "simpleFeatures")) => {
    // Extraction logic goes here
    }
  }
})

Возможно, я не совсем хорошо разбираюсь в Scala и json4s, поэтому любой совет приветствуется.

json4s version
3.2.10

scala version
2.11.12

jdk version
1.8.0

Ответы [ 2 ]

1 голос
/ 01 мая 2020

Я думаю, вам нужно расширить CustomSerializer класс, поскольку CustomKeySerializer он используется для реализации пользовательских логи c для JSON ключей:

import org.json4s.{CustomSerializer, MappingException}
import org.json4s.JsonAST._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._

case class SimpleFeature(column: String,
                          valueType: String,
                          nullValue: String,
                          func: Option[String])

case class TaskConfig(simpleFeatures: Option[Seq[SimpleFeature]])

object Main extends App {

implicit val formats = new DefaultFormats {
    override val strictOptionParsing: Boolean = true
  } + new SimpleFeatureSerializer()

  class SimpleFeatureSerializer extends CustomSerializer[SimpleFeature](_ => ( {
    case jsonObj: JObject =>
      val requiredKeys = Set[String]("column", "valueType", "nullValue")

      val diff = requiredKeys.diff(jsonObj.values.keySet)
      if (diff.nonEmpty)
        throw new MappingException(s"Fields [${requiredKeys.mkString(",")}] are mandatory. Missing fields: [${diff.mkString(",")}]")

      val column = (jsonObj \ "column").extract[String]
      val valueType = (jsonObj \ "valueType").extract[String]
      val nullValue = (jsonObj \ "nullValue").extract[String]
      val func = (jsonObj \ "func").extract[Option[String]]

      SimpleFeature(column, valueType, nullValue, func)
  }, {
    case sf: SimpleFeature =>
      ("column" -> sf.column) ~
        ("valueType" -> sf.valueType) ~
        ("nullValue" -> sf.nullValue) ~
        ("func" -> sf.func)
  }
  ))

  // case 1: Test single feature
  val singleFeature  = """
          {
              "column": "pcat_id",
              "valueType": "categorical",
              "nullValue": "DUMMY"
          }
      """
  val singleFeatureValid = parse(singleFeature).extract[SimpleFeature]
  println(singleFeatureValid)
  //  SimpleFeature(pcat_id,categorical,DUMMY,None)

  // case 2: Test task config
  val taskConfig  = """{
      "simpleFeatures": [
        {
          "column": "pcat_id",
          "valueType": "categorical",
          "nullValue": "DUMMY"
        },
        {
          "column": "brand_code",
          "valueType": "categorical",
          "nullValue": "DUMMY"
        }]
  }"""

  val taskConfigValid = parse(taskConfig).extract[TaskConfig]
  println(taskConfigValid)
  //  TaskConfig(List(SimpleFeature(pcat_id,categorical,DUMMY,None), SimpleFeature(brand_code,categorical,DUMMY,None)))

  // case 3: Invalid json
  val invalidSingleFeature  = """
          {
              "column": "pcat_id",
              "value": "categorical",
              "nullValue": "DUMMY"
          }
      """
  val singleFeatureInvalid = parse(invalidSingleFeature).extract[SimpleFeature]
  // throws MappingException
}

Анализ : главный вопрос здесь - как получить доступ к ключам jsonObj, чтобы проверить, есть ли недействительный или отсутствующий ключ, один из способов добиться этого - через jsonObj.values.keySet. Для реализации сначала мы присваиваем обязательные поля переменной requiredKeys, затем сравниваем requiredKeys с теми, которые в настоящее время присутствуют, с requiredKeys.diff(jsonObj.values.keySet). Если разница не пустая, это означает, что пропущено обязательное поле, в этом случае мы выдаем исключение, включающее необходимую информацию.

Примечание 1: мы не должны забывать добавить новый сериализатор в доступные форматы.

Примечание 2: мы бросаем экземпляр MappingException, который уже используется json4s для внутреннего анализа при разборе строки JSON.

UPDATE

Для принудительной проверки полей Option необходимо установить для параметра strictOptionParsing значение true, переопределив соответствующий метод:

implicit val formats = new DefaultFormats {
    override val strictOptionParsing: Boolean = true
  } + new SimpleFeatureSerializer()

Resources

https://nmatpt.com/blog/2017/01/29/json4s-custom-serializer/

https://danielasfregola.com/2015/08/17/spray-how-to-deserialize-entities-with-json4s/

https://www.programcreek.com/scala/org.json4s.CustomSerializer

0 голосов
/ 01 мая 2020

Вы можете попробовать использовать play.api.libs.json

"com.typesafe.play" %% "play-json" % "2.7.2",
"net.liftweb" % "lift-json_2.11" % "2.6.2" 

Вам просто нужно определить класс дела и форматеры.

Пример:

case class Example(a: String, b: String)

implicit val formats: DefaultFormats.type = DefaultFormats
implicit val instancesFormat= Json.format[Example]

, а затем просто сделать :

Json.parse(jsonData).asOpt[Example]

В случае, если приведенное выше дает некоторые ошибки: Попробуйте добавить "net.liftweb" % "lift-json_2.11" % "2.6.2" также в вашу зависимость.

...