Skip to content

Commit

Permalink
fix records with Option fields with defaults other than None
Browse files Browse the repository at this point in the history
  • Loading branch information
mberndt123 committed Apr 19, 2024
1 parent aa6714a commit 69b849f
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
package com.sksamuel.avro4s.decoders

import com.sksamuel.avro4s.{Decoder, Encoder}
import com.sksamuel.avro4s.Decoder
import com.sksamuel.avro4s.Encoder
import org.apache.avro.Schema

import scala.jdk.CollectionConverters.*

class OptionDecoder[T](decoder: Decoder[T]) extends Decoder[Option[T]] {

override def decode(schema: Schema): Any => Option[T] = {
// nullables must be encoded with a union of 2 elements, where null is the first type
// nullables must be encoded with a union of 2 elements, one of which is null
require(schema.getType == Schema.Type.UNION, {
"Options can only be encoded with a UNION schema"
})
require(schema.getTypes.size() >= 2, {
"An option should be encoded with a UNION schema with at least 2 element types"
})
require(schema.getTypes.get(0).getType == Schema.Type.NULL, {
"Options can only be encoded with a UNION schema with NULL as the first element type"
})
val schemaSize = schema.getTypes.size()
val elementSchema = schemaSize match
case 2 => schema.getTypes.get(1)
case _ => Schema.createUnion(schema.getTypes.subList(1, schemaSize))
val (isNull, isNotNull) = schema.getTypes().asScala.toList.partition(_.getType() == Schema.Type.NULL)
require(isNull.size == 1, s"exactly one of the schemas must be null (found ${isNull.size})")
val elementSchema = isNotNull match {
case List(single) => single
case more => Schema.createUnion(more.asJava)
}

val decode = decoder.decode(elementSchema)
{ value => if (value == null) None else Some(decode(value)) }
}
}

trait OptionDecoders:
given[T](using decoder: Decoder[T]): Decoder[Option[T]] = new OptionDecoder[T](decoder)
given[T](using decoder: Decoder[T]): Decoder[Option[T]] = new OptionDecoder[T](decoder)
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
package com.sksamuel.avro4s.encoders

import com.sksamuel.avro4s.{Encoder, FieldMapper}
import com.sksamuel.avro4s.Encoder
import com.sksamuel.avro4s.FieldMapper
import org.apache.avro.Schema

import scala.jdk.CollectionConverters.*

class OptionEncoder[T](encoder: Encoder[T]) extends Encoder[Option[T]] {

override def encode(schema: Schema): Option[T] => Any = {
// nullables must be encoded with a union of 2 elements, where null is the first type
// nullables must be encoded with a union of 2 elements, one of which is null
require(schema.getType == Schema.Type.UNION, {
"Options can only be encoded with a UNION schema"
})
require(schema.getTypes.size() >= 2, {
"Options can only be encoded with a union schema with 2 or more types"
})
require(schema.getTypes.get(0).getType == Schema.Type.NULL)
val schemaSize = schema.getTypes.size()
val elementSchema = schemaSize match
case 2 => schema.getTypes.get(1)
case _ => Schema.createUnion(schema.getTypes.subList(1, schemaSize))
val (isNull, isNotNull) = schema.getTypes().asScala.toList.partition(_.getType() == Schema.Type.NULL)
require(isNull.size == 1, s"exactly one of the schemas must be null (found ${isNull.size})")

val elementSchema = isNotNull match {
case List(single) => single
case more => Schema.createUnion(more.asJava)
}
val elementEncoder = encoder.encode(elementSchema)
{ option => option.fold(null)(value => elementEncoder(value)) }
{ option => option.fold(null)(elementEncoder) }
}
}

trait OptionEncoders:
given[T](using encoder: Encoder[T]): Encoder[Option[T]] = OptionEncoder[T](encoder)
given[T](using encoder: Encoder[T]): Encoder[Option[T]] = OptionEncoder[T](encoder)

0 comments on commit 69b849f

Please sign in to comment.