【问题标题】:Scala: How convert nested/complicated data to avro using avro4s?Scala:如何使用 avro4s 将嵌套/复杂数据转换为 avro?
【发布时间】:2019-03-01 21:52:27
【问题描述】:

我有 Scala 2.12 并导入了库 avro4s,按照我的要求遵循 link

基本上,我的 avro 架构如下: 仅示例:

Schema: {"name": "person","type": "record","fields": [{"name": "address","type": {"type" : "record","name" : "AddressUSRecord","fields" : [{"name": "streetaddress", "type": "string"},{"name": "city", "type":"string"}]}}]}

所以我创建了 3 个案例类。

我根据这些类测试了架构,看起来不错。
所以,模式生成是好的。

现在,我正在根据案例类创建所需的对象。

当我尝试编写 avro 文件时,出现空指针异常。

错误:

Exception in thread "main" java.lang.NullPointerException
    at org.apache.avro.util.Utf8$2.toUtf8(Utf8.java:123)
    at org.apache.avro.util.Utf8.getBytesFor(Utf8.java:172)
    at org.apache.avro.util.Utf8.<init>(Utf8.java:39)
    at com.sksamuel.avro4s.Encoder$StringEncoder$.encode(Encoder.scala:73)
    at com.sksamuel.avro4s.Encoder$StringEncoder$.encode(Encoder.scala:68)
    at com.sksamuel.avro4s.Encoder$.encodeField(Encoder.scala:401)
    at com.sksamuel.avro4s.Encoder$.encodeFieldLazy(Encoder.scala:379)
    at MyClass$$anon$4$$anon$5.encode(MyClass.scala:90)
    at MyClass$$anon$4$$anon$5.encode(MyClass.scala:90)
    at com.sksamuel.avro4s.Encoder$.encodeField(Encoder.scala:401)
    at com.sksamuel.avro4s.Encoder$.encodeFieldNotLazy(Encoder.scala:373)
    at MyClass$$anon$4.encode(MyClass.scala:90)
    at MyClass$$anon$4.encode(MyClass.scala:90)
    at com.sksamuel.avro4s.AvroDataOutputStream.$anonfun$x$1$2(AvroDataOutputStream.scala:35)
    at com.sksamuel.avro4s.AvroDataOutputStream.$anonfun$x$1$2$adapted(AvroDataOutputStream.scala:34)
    at com.sksamuel.avro4s.AvroDataOutputStream.write(AvroDataOutputStream.scala:46)
    at MyClass$.delayedEndpoint$MyClass$1(MyClass.scala:91)
    at MyClass$delayedInit$body.apply(MyClass.scala:42)
    at scala.Function0.apply$mcV$sp(Function0.scala:34)
    at scala.Function0.apply$mcV$sp$(Function0.scala:34)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App.$anonfun$main$1$adapted(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:388)
    at scala.App.main(App.scala:76)
    at scala.App.main$(App.scala:74)
    at MyClass$.main(MyClass.scala:42)
    at MyClass.main(MyClass.scala)

代码:

//import java.io.File
import com.sksamuel.avro4s.{AvroOutputStream, AvroSchema}
import java.io.File

//case class Person(name: String, age: Int)
//case class Book(title: String, year: Int, owner: Person, authors: Seq[Person])
// case class as per schema

object MyClass extends App {
  val outFile = "/path/TestScala.avro"
   // val schema = AvroSchema[Book]
  println("Hello, World!")
   // println(schema)


  val head = header(
    prop1="val1"
    prop2=null

  )

  val pnlBody = pnlData(
    <corresponsing property vlaues, some with null>
  )

  val record = MyClass(header = head, body = pnlBody)

  val schema = AvroSchema[MyClass]
  println(schema)
  println(record)

  val os = AvroOutputStream.data[MyClass].to(new File(outFile)).build(schema)
  os.write(record)
  os.flush()
  os.close()

}

基本上,根据我拥有的架构,我想了解我的最终记录对象应该如何?

更新

根据@Antot 和@Daniel 的以下建议,我将我的标题和正文类更改为对所有预期为空的值使用 Option[String]。但还是同样的问题。

对标头和数据的案例类的更改,产生了以下模式和记录。以下记录是否正确创建?

请指教?

更新 2:
我认为 Null 的问题。预计记录的属性很少为 NULL。由于我更改为 Option[String],它的值应该是 None 而不是 null。我是 Scala 新手,所以仍然了解它的数据类型。

因此,现在可以将值从 null 更改为 None。

但是,我还有一个问题。 如果我的属性是 Option[String],它如何转换为 Avro?如果我的值为 None,它是否会转换为 Avro null?

【问题讨论】:

  • 这个问题似乎与可为空的字符串字段有关。您是否尝试将可能具有 =null 值的每个字段的类型设置为 Option[String]
  • 添加到@Antot 的答案。您无法设置 null,因为您的架构中不接受 null。对于任何可以为空的字段,您都应该使用联合类型。例如{ "name" : "sourceLocation", "type": ["string", "null"] }
  • avro4s 是 @monkjack 的项目。如果可以请帮忙?

标签: scala avro avro4s


【解决方案1】:

您的问题是您的架构未将字段定义为可为空的。如果您有空值,则架构中必须支持该值。要在 Avro 中执行此操作,您必须创建两个模式的“联合”——一个是 NULL 模式,一个是“真实类型”。例如,看看这个架构。

{
  "type": "record",
  "name": "MyClass",
  "namespace": "com.sksamuel.avro4s",
  "fields": [
    {
      "name": "a",
      "type": [
        "null",
        "string"
      ],
      "default": null
    }
  ]
}

这是一种记录类型com.sksamuel.avro4s.MyClass,它有一个字段a。那么a 的类型可以是nullstring。因此,在写出这种类型的记录时,您可以使用 null 或字符串作为字段 a

现在,您不必手动创建此架构(就像您在帖子中所做的那样)。您可以使用 AvroSchema 宏根据案例类为您施展魔法。

val schema = AvroSchema[MyClass]

使用此宏时,如果您将类型定义为Option[T],将创建一个可为空的联合。所以你可以这样做,

case class MyClass(a: Option[String])

您最终会得到与上述相同的架构。如果你这样做了,

case class MyClass(a: String)

那么架构将是:

{
  "type": "record",
  "name": "MyClass",
  "namespace": "com.sksamuel.avro4s",
  "fields": [
    {
      "name": "a",
      "type": "string"
    }
  ]
}

tl;博士

要么从可空字段定义为选项的案例类创建架构,要么更新您的手动架构以使用 {null, T} 的联合。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-24
    • 2018-07-27
    • 1970-01-01
    • 2020-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多