{"id":13766601,"url":"https://github.com/sksamuel/avro4s","last_synced_at":"2025-05-13T19:04:10.894Z","repository":{"id":37550429,"uuid":"42593679","full_name":"sksamuel/avro4s","owner":"sksamuel","description":"Avro schema generation and serialization / deserialization for Scala","archived":false,"fork":false,"pushed_at":"2025-04-25T18:30:48.000Z","size":2833,"stargazers_count":723,"open_issues_count":32,"forks_count":240,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-05-13T06:49:38.385Z","etag":null,"topics":["avro","avro-schema","coproduct","scala","scala-macros","schema-generation","serialization"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sksamuel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-09-16T14:50:22.000Z","updated_at":"2025-05-07T09:12:46.000Z","dependencies_parsed_at":"2023-02-16T23:35:40.440Z","dependency_job_id":"108180dc-70b3-4413-a29c-f5d3835e5ecf","html_url":"https://github.com/sksamuel/avro4s","commit_stats":{"total_commits":1335,"total_committers":108,"mean_commits":12.36111111111111,"dds":"0.32659176029962544","last_synced_commit":"bf98c4ccc4ad30180d88cf577c8a3a7dd3261a8a"},"previous_names":[],"tags_count":69,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sksamuel%2Favro4s","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sksamuel%2Favro4s/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sksamuel%2Favro4s/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sksamuel%2Favro4s/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sksamuel","download_url":"https://codeload.github.com/sksamuel/avro4s/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254010796,"owners_count":21998993,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["avro","avro-schema","coproduct","scala","scala-macros","schema-generation","serialization"],"created_at":"2024-08-03T16:00:58.073Z","updated_at":"2025-05-13T19:04:10.879Z","avatar_url":"https://github.com/sksamuel.png","language":"Scala","funding_links":[],"categories":["Libraries","Table of Contents","Serialization","\u003ca name=\"Scala\"\u003e\u003c/a\u003eScala"],"sub_categories":["Serialization"],"readme":"# ![logo.png](logo.png)\n\n![build](https://github.com/sksamuel/avro4s/workflows/master/badge.svg)\n[\u003cimg src=\"https://img.shields.io/maven-central/v/com.sksamuel.avro4s/avro4s-core_2.12.svg?label=latest%20release%20for%202.12\"/\u003e](http://search.maven.org/#search%7Cga%7C1%7Cavro4s-core_2.12)\n[\u003cimg src=\"https://img.shields.io/maven-central/v/com.sksamuel.avro4s/avro4s-core_2.13.svg?label=latest%20release%20for%202.13\"/\u003e](http://search.maven.org/#search%7Cga%7C1%7Cavro4s-core_2.13)\n[\u003cimg src=\"https://img.shields.io/maven-central/v/com.sksamuel.avro4s/avro4s-core_3.svg?label=latest%20release%20for%203.0\"/\u003e](http://search.maven.org/#search%7Cga%7C1%7Cavro4s-core_3)\n[\u003cimg src=\"https://img.shields.io/nexus/s/https/oss.sonatype.org/com.sksamuel.avro4s/avro4s-core_3.svg?label=latest%20snapshot\u0026style=plastic\"/\u003e](https://oss.sonatype.org/content/repositories/snapshots/com/sksamuel/avro4s/)\n\n**This is a community project - PRs will be accepted and releases published by the maintainer**\n\nAvro4s is a schema/class generation and serializing/deserializing library for [Avro](http://avro.apache.org/) written in Scala. The objective is to allow seamless use with Scala without the need to write boilerplate conversions yourself, and without the runtime overhead of reflection. Hence, this is a macro based library and generates code for use with Avro at _compile time_.\n\nThe features of the library are:\n* Schema generation from classes at compile time\n* Boilerplate free serialization of Scala types into Avro types\n* Boilerplate free deserialization of Avro types to Scala types\n\n## Versioning\n\nThe `master` branch contains version 5.0.x which is designed for Scala 3. PRs are welcome. This version may have minor breaking changes compared to previous major release in order to support the new features of Scala 3.\n\nThe previous major version is 4.0.x located at branch `release/4.0.x` and is released for Scala 2.12 and Scala 2.13.\nThis version is in support mode only. Bug reports are welcome and bug fixes will be released. No new features will be\nadded.\n\nPlease raise PRs using branch names `scala2/*` and `scala3/*` depending on which version of Scala your work is\ntargeting.\n\n## Schemas\n\nUnlike Json, Avro is a schema based format. You'll find yourself wanting to generate schemas frequently, and writing\nthese by hand or through the Java based `SchemaBuilder` classes can be tedious for complex domain models. Avro4s allows\nus to generate schemas directly from case classes at compile time via macros. This gives you both the convenience of\ngenerated code, without the annoyance of having to run a code generation step, as well as avoiding the peformance\npenalty of runtime reflection based code.\n\nLet's define some classes.\n\n```scala\ncase class Ingredient(name: String, sugar: Double, fat: Double)\ncase class Pizza(name: String, ingredients: Seq[Ingredient], vegetarian: Boolean, vegan: Boolean, calories: Int)\n```\n\nTo generate an Avro Schema, we need to use the `AvroSchema` object passing in the target type as a type parameter.\nThis will return an `org.apache.avro.Schema` instance.\n\n```scala\nimport com.sksamuel.avro4s.AvroSchema\nval schema = AvroSchema[Pizza]\n```\n\nWhere the generated schema is as follows:\n\n```json\n{\n   \"type\":\"record\",\n   \"name\":\"Pizza\",\n   \"namespace\":\"com.sksamuel\",\n   \"fields\":[\n      {\n         \"name\":\"name\",\n         \"type\":\"string\"\n      },\n      {\n         \"name\":\"ingredients\",\n         \"type\":{\n            \"type\":\"array\",\n            \"items\":{\n               \"type\":\"record\",\n               \"name\":\"Ingredient\",\n               \"fields\":[\n                  {\n                     \"name\":\"name\",\n                     \"type\":\"string\"\n                  },\n                  {\n                     \"name\":\"sugar\",\n                     \"type\":\"double\"\n                  },\n                  {\n                     \"name\":\"fat\",\n                     \"type\":\"double\"\n                  }\n               ]\n            }\n         }\n      },\n      {\n         \"name\":\"vegetarian\",\n         \"type\":\"boolean\"\n      },\n      {\n         \"name\":\"vegan\",\n         \"type\":\"boolean\"\n      },\n      {\n         \"name\":\"calories\",\n         \"type\":\"int\"\n      }\n   ]\n}\n```\nYou can see that the schema generator handles nested case classes, sequences, primitives, etc. For a full list of supported object types, see the table later.\n\n### Overriding class name and namespace\n\nAvro schemas for complex types (RECORDS) contain a name and a namespace. By default, these are the name of the class\nand the enclosing package name, but it is possible to customize these using the annotations `AvroName` and `AvroNamespace`.\n\nFor example, the following class:\n\n```scala\npackage com.sksamuel\ncase class Foo(a: String)\n```\n\nWould normally have a schema like this:\n\n```json\n{\n  \"type\":\"record\",\n  \"name\":\"Foo\",\n  \"namespace\":\"com.sksamuel\",\n  \"fields\":[\n    {\n      \"name\":\"a\",\n      \"type\":\"string\"\n    }\n  ]\n}\n```\n\nHowever we can override the name and/or the namespace like this:\n\n```scala\npackage com.sksamuel\n\n@AvroName(\"Wibble\")\n@AvroNamespace(\"com.other\")\ncase class Foo(a: String)\n```\n\nAnd then the generated schema looks like this:\n\n```json\n{\n  \"type\":\"record\",\n  \"name\":\"Wibble\",\n  \"namespace\":\"com.other\",\n  \"fields\":[\n    {\n      \"name\":\"a\",\n      \"type\":\"string\"\n    }\n  ]\n}\n```\n\nNote: It is possible, but not necessary, to use both AvroName and AvroNamespace. You can just use either of them if you wish.\n\n### Overriding a field name\n\nThe `AvroName` annotation can also be used to override field names. This is useful when the record instances you are generating or reading need to have field names different from the scala case classes. For example if you are reading data generated by another system, or another language.\n\nGiven the following class.\n\n```scala\npackage com.sksamuel\ncase class Foo(a: String, @AvroName(\"z\") b : String)\n```\n\nThen the generated schema would look like this:\n\n```json\n{\n  \"type\":\"record\",\n  \"name\":\"Foo\",\n  \"namespace\":\"com.sksamuel\",\n  \"fields\":[\n    {\n      \"name\":\"a\",\n      \"type\":\"string\"\n    },\n    {\n      \"name\":\"z\",\n      \"type\":\"string\"\n    }    \n  ]\n}\n```\n\nNotice that the second field is `z` and not `b`.\n\nNote: @AvroName does not add an alternative name for the field, but an override. If you wish to have alternatives then you want to use @AvroAlias.\n\n### Adding properties and docs to a Schema\n\nAvro allows a doc field, and arbitrary key/values to be added to generated schemas. Avro4s supports this through the use of `AvroDoc` and `AvroProp` annotations.\n\nThese properties works on either complex or simple types - in other words, on both fields and classes. For example:\n\n```scala\npackage com.sksamuel\n@AvroDoc(\"hello, is it me you're looking for?\")\ncase class Foo(@AvroDoc(\"I am a string\") str: String, @AvroDoc(\"I am a long\") long: Long, int: Int)\n```\n\nWould result in the following schema:\n\n```json\n{  \n  \"type\": \"record\",\n  \"name\": \"Foo\",\n  \"namespace\": \"com.sksamuel\",\n  \"doc\":\"hello, is it me you're looking for?\",\n  \"fields\": [  \n    {  \n      \"name\": \"str\",\n      \"type\": \"string\",\n      \"doc\" : \"I am a string\"\n    },\n    {  \n      \"name\": \"long\",\n      \"type\": \"long\",\n      \"doc\" : \"I am a long\"\n    },\n    {  \n      \"name\": \"int\",\n      \"type\": \"int\"\n    }\n  ]\n}\n```\n\nAn example of properties:\n\n```scala\npackage com.sksamuel\n@AvroProp(\"jack\", \"bruce\")\ncase class Annotated(@AvroProp(\"richard\", \"ashcroft\") str: String, @AvroProp(\"kate\", \"bush\") long: Long, int: Int)\n```\n\nWould generate this schema:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"Annotated\",\n  \"namespace\": \"com.sksamuel\",\n  \"fields\": [\n    {\n      \"name\": \"str\",\n      \"type\": \"string\",\n      \"richard\": \"ashcroft\"\n    },\n    {\n      \"name\": \"long\",\n      \"type\": \"long\",\n      \"kate\": \"bush\"\n    },\n    {\n      \"name\": \"int\",\n      \"type\": \"int\"\n    }\n  ],\n  \"jack\": \"bruce\"\n}\n```\n\n### Overriding a Schema\n\nBehind the scenes, `AvroSchema` uses an implicit `SchemaFor`. This is the core typeclass which generates an Avro schema for a given Java or Scala type. There are `SchemaFor` instances for all the common JDK and SDK types, as well as macros that generate instances for case classes.\n\nIn order to override how a schema is generated for a particular type you need to bring into scope an implicit `SchemaFor` for the type you want to override. As an example, lets say you wanted all integers to be encoded as `Schema.Type.STRING` rather than the standard `Schema.Type.INT`.\n\nTo do this, we just introduce a new instance of `SchemaFor` and put it in scope when we generate the schema.\n\n```scala\nimplicit val intOverride = SchemaFor[Int](SchemaBuilder.builder.stringType)\n\ncase class Foo(a: Int)\nval schema = AvroSchema[Foo]\n```\n\nNote: If you create an override like this, be aware that schemas in Avro are mutable, so don't share the values that the typeclasses return.\n\n### Transient Fields\n\nAvro4s does not support the @transient anotation to mark a field as ignored, but instead supports its own @AvroTransient annotation to do the same job. Any field marked with this will be excluded from the generated schema.\n\n```scala\npackage com.sksamuel\ncase class Foo(a: String, @AvroTransient b: String)\n```\n\nWould result in the following schema:\n\n```json\n{  \n  \"type\": \"record\",\n  \"name\": \"Foo\",\n  \"namespace\": \"com.sksamuel\",\n  \"fields\": [  \n    {  \n      \"name\": \"a\",\n      \"type\": \"string\"\n    }\n  ]\n}\n```\n\n### Field Mapping\n\nIf you are dealing with Avro data generated in other languages then it's quite likely the field names will reflect the style of that language. For example, Java may prefer `camelCaseFieldNames` but other languages may use `snake_case_field_names` or `PascalStyleFieldNames`. By default the name of the field in the case class is what will be used, and you've seen earlier that you can override a specific field with @AvroName, but doing this for every single field would be insane.\n\nSo, avro4s provides a `FieldMapper` for this. You simply bring into scope an instance of `FieldMapper` that will convert the scala field names into a target type field names.\n\nFor example, lets take a scala case and generate a schema using snake case.\n\n```scala\npackage com.sksamuel\ncase class Foo(userName: String, emailAddress: String)\nimplicit val snake: FieldMapper = SnakeCase\nval schema = AvroSchema[Foo]\n```\n\nWould generate the following schema:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"Foo\",\n  \"namespace\": \"com.sksamuel\",\n  \"fields\": [\n    {\n      \"name\": \"user_name\",\n      \"type\": \"string\"\n    },\n    {\n      \"name\": \"email_address\",\n      \"type\": \"string\"\n    }\n  ]\n}\n```\n\nYou can also define your own field mapper:\n\n```scala\npackage com.sksamuel\ncase class Foo(userName: String, emailAddress: String)\nimplicit val short: FieldMapper = {\n  case \"userName\"     =\u003e \"user\"\n  case \"emailAddress\" =\u003e \"email\"\n}\nval schema = AvroSchema[Foo]\n```\n\nWould generate the following schema:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"Foo\",\n  \"namespace\": \"com.sksamuel\",\n  \"fields\": [\n    {\n      \"name\": \"user\",\n      \"type\": \"string\"\n    },\n    {\n      \"name\": \"email\",\n      \"type\": \"string\"\n    }\n  ]\n}\n```\n\n### Field Defaults\n\nAvro4s will take into account default values on fields. For example, the following class `case class Wibble(s: String = \"foo\")` would be serialized as:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"Wibble\",\n  \"namespace\": \"com.sksamuel.avro4s.schema\",\n  \"fields\": [\n    {\n      \"name\": \"s\",\n      \"type\": \"string\",\n      \"default\" : \"foo\"\n    }\n  ]\n}\n```\n\nHowever if you wish the scala default to be ignored, then you can annotate the field with @AvroNoDefault. So this class `case class Wibble(@AvroNoDefault s: String = \"foo\")` would be serialized as:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"Wibble\",\n  \"namespace\": \"com.sksamuel.avro4s.schema\",\n  \"fields\": [\n    {\n      \"name\": \"s\",\n      \"type\": \"string\"\n    }\n  ]\n}\n```\n\n### Enums and Enum Defaults\n\n#### AVRO Enums from Scala Enums, Java Enums, and Sealed Traits\n\nAvro4s maps scala enums, java enums, and scala sealed traits to the AVRO `enum` type.\nFor example, the following scala enum:\n```scala\nobject Colours extends Enumeration {\n  val Red, Amber, Green = Value\n}\n```\nwhen referenced in a case class:\n```scala\ncase class Car(colour: Colours.Value)\n```\nresults in the following AVRO schema (e.g. using `val schema = AvroSchema[Car]`):\n```json\n{\n  \"type\" : \"record\",\n  \"name\" : \"Car\",\n  \"fields\" : [ {\n    \"name\" : \"colour\",\n    \"type\" : {\n      \"type\" : \"enum\",\n      \"name\" : \"Colours\",\n      \"symbols\" : [ \"Red\", \"Amber\", \"Green\" ]\n    }\n  } ]\n}\n```\nAvro4s will also convert a Java enum such as:\n```java\npublic enum Wine {\n    Malbec, Shiraz, CabSav, Merlot\n}\n```\ninto an AVRO `enum` type:\n```json\n{\n  \"type\": \"enum\",\n  \"name\": \"Wine\",\n  \"symbols\": [ \"Malbec\", \"Shiraz\", \"CabSav\", \"Merlot\" ]\n}\n```\nAnd likewise, avro4s will convert a sealed trait such as:\n```scala\nsealed trait Animal\n@AvroSortPriority(0) case object Cat extends Animal\n@AvroSortPriority(-1) case object Dog extends Animal\n```\ninto the following AVRO `enum` schema:\n```json\n{\n  \"type\" : \"enum\",\n  \"name\" : \"Animal\",\n  \"symbols\" : [ \"Cat\", \"Dog\" ]\n}\n```\n\nWith `@AvroSortPriority` attribute, elements are sorted in descending order, by the priority specified\n(the element with the highest priority will be put as first).\n\nAccording to Avro specification, when an element is not found the first compatible element defined in the union is used.\nFor this reason order of the elements should not be changed when compatibility is important.\nAdd new elements at the end.\n\nAn alternative solution is to use the `@AvroUnionPosition` attribute passing a number that will be sorted ascending,\nfrom lower to upper:\n\n```scala\n  sealed trait Fruit\n  @AvroUnionPosition(0)\n  case object Unknown extends Fruit\n  @AvroUnionPosition(1)\n  case class Orange(size: Int) extends Fruit\n  @AvroUnionPosition(2)\n  case class Mango(size: Int) extends Fruit\n```\n\nThis will generate the following AVRO schema:\n```json\n[\n    {\n        \"type\" : \"record\",\n        \"name\" : \"Unknown\",\n        \"fields\" : [ ]\n    },\n    {\n        \"type\" : \"record\",\n        \"name\" : \"Orange\",\n        \"fields\" : [ {\n            \"name\" : \"size\",\n            \"type\" : \"int\"\n        } ]\n    },\n    {\n        \"type\" : \"record\",\n        \"name\" : \"Mango\",\n        \"fields\" : [ {\n            \"name\" : \"size\",\n            \"type\" : \"int\"\n        } ]\n    }\n]\n``` \n\n#### Field Defaults vs. Enum Defaults\n\nAs with any AVRO field, you can specify an enum field's default value as follows:\n```scala\ncase class Car(colour: Colours.Value = Colours.Red)\n```\nresulting in the following AVRO schema:\n```json\n{\n  \"type\" : \"record\",\n  \"name\" : \"Car\",\n  \"fields\" : [ {\n    \"name\" : \"colour\",\n    \"type\" : {\n      \"type\" : \"enum\",\n      \"name\" : \"Colours\",\n      \"symbols\" : [ \"Red\", \"Amber\", \"Green\" ]\n    },\n    \"default\": \"Red\"\n  } ]\n}\n```\nOne benefit of providing a field default is that the writer can later remove the field without\nbreaking existing readers. In the `Car` example, if the writer doesn't provide a value for the\n`colour` field, the reader will default the `colour` to `Red`.\n\nBut what if the writer would like to extend the `Colour` enumeration to include the colour `Orange`:\n```scala\nobject Colours extends Enumeration {\n  val Red, Amber, Green, Orange = Value\n}\n```\nresulting in the following AVRO schema?\n```json\n{\n  \"type\" : \"record\",\n  \"name\" : \"Car\",\n  \"fields\" : [ {\n    \"name\" : \"colour\",\n    \"type\" : {\n      \"type\" : \"enum\",\n      \"name\" : \"Colours\",\n      \"symbols\" : [ \"Red\", \"Amber\", \"Green\", \"Orange\" ]\n    },\n    \"default\": \"Red\"\n  } ]\n}\n```\nIf a writer creates an `Orange` `Car`:\n```scala\nCar(colours = Colours.Orange)\n```\nreaders using the older schema (the one without the new `Orange` value), will fail with a backwards compatibility error.\nI.e. readers using the previous version of the `Car` schema don't know the colour `Orange`, and therefore\ncan't read the new `Car` record.\n\nTo enable writers to extend enums in a backwards-compatible way, AVRO allows you to specify a default enum value \nas part of the enum type's definition:\n```json\n{\n  \"type\" : \"enum\",\n  \"name\" : \"Colours\",\n  \"symbols\" : [ \"Red\", \"Amber\", \"Green\" ],\n  \"default\": \"Amber\"\n}\n```\nNote that an enum's default isn't the same as an enum field's default as showed below,\nwhere the enum default is `Amber` and the field's default is `Red`:\n```json\n{\n  \"type\" : \"record\",\n  \"name\" : \"Car\",\n  \"fields\" : [ {\n    \"name\" : \"colour\",\n    \"type\" : {\n      \"type\" : \"enum\",\n      \"name\" : \"Colours\",\n      \"symbols\" : [ \"Red\", \"Amber\", \"Green\" ],\n      \"default\": \"Amber\"\n    },\n    \"default\": \"Red\"\n  } ]\n}\n```\nNote that the field's default and the enum's default need not be the same value.\n\nThe field's default answers the question:\n* What value should the reader use if the writer didn't specify the field's value?\n\nIn the schema example above, the answer is `Red`.\n\nThe enum's default value answers the question:\n* What value should the reader use if the writer specifies an enum value that the reader doesn't recognize?\n\nIn the example above, the answer is `Amber`.\n\nIn summary, as long as a writer specified a the default enum value in previous versions of an enum's schema, the writer can add\nnew enum values without breaking older readers. For example, we can add\nthe colour `Orange` to the `Colour` enum's list of symbol/values without breaking older readers:\n```json\n{\n  \"type\" : \"record\",\n  \"name\" : \"Car\",\n  \"fields\" : [ {\n    \"name\" : \"colour\",\n    \"type\" : {\n      \"type\" : \"enum\",\n      \"name\" : \"Colours\",\n      \"symbols\" : [ \"Red\", \"Amber\", \"Green\", \"Orange\" ],\n      \"default\": \"Amber\"\n    },\n    \"default\": \"Red\"\n  } ]\n}\n```\nSpecifically, given `Amber` as the enum's default, an older AVRO reader that receives an `Orange` `Car` will \ndefault the `Car`'s `colour` to `Amber`, the enum's default.\n\nThe following sections describe how to define enum defaults through avro4s for scala enums, java enums,\nand sealed traits.\n\n#### Defining Enum Defaults for Scala Enums\n\nFor scala enums such as:\n```scala\nobject Colours extends Enumeration {\n   val Red, Amber, Green = Value\n}\n ```\navro4s gives you two options:\n1) You can define an implicit `SchemaFor` using the `ScalaEnumSchemaFor[E].apply(default: E)` method\nwhere the method's `default` argument is one of the enum's values or ...\n2) You can use the `@AvroEnumDefault` annotation to declare the default enum value.\n\nFor example, to create an implicit `SchemaFor` for an scala enum with a default enum value, \nuse the `ScalaEnumSchemaFor[E].apply(default: E)` method as follows:\n```scala\nimplicit val schemaForColours: SchemaFor[Colours.Value] = ScalaEnumSchemaFor[Colours.Value](default = Colours.Amber)\n```\nresulting in the following AVRO schema:\n```json\n{\n  \"type\" : \"enum\",\n  \"name\" : \"Colours\",\n  \"symbols\" : [ \"Red\", \"Amber\", \"Green\" ],\n  \"default\": \"Amber\"\n}\n```\nOr, to declare the default enum value, you can use the `@AvroEnumDefault` annotation as follows:\n```scala\n@AvroEnumDefault(Colours.Amber)\nobject Colours extends Enumeration {\n   val Red, Amber, Green = Value\n}\n```\nresulting in the same AVRO schema:\n```json\n{\n  \"type\" : \"enum\",\n  \"name\" : \"Colours\",\n  \"symbols\" : [ \"Red\", \"Amber\", \"Green\" ],\n  \"default\": \"Amber\"\n}\n```\nYou can also use the following avro4s annotations to change a scala enum's name, namespace, and to add additional properties:\n* `@AvroName`\n* `@AvroNamespace`\n* `@AvroProp`\n\nFor example:\n```scala\n@AvroName(\"MyColours\")\n@AvroNamespace(\"my.namespace\")\n@AvroEnumDefault(Colours.Green)\n@AvroProp(\"hello\", \"world\")\nobject Colours extends Enumeration {\n  val Red, Amber, Green = Value\n}\n```\nresulting in the following AVRO schema:\n```json\n{\n  \"type\" : \"enum\",\n  \"name\" : \"MyColours\",\n  \"namespace\" : \"my.namespace\",\n  \"symbols\" : [ \"Red\", \"Amber\", \"Green\" ],\n  \"default\": \"Amber\",\n  \"hello\" : \"world\"\n}\n```\nNote that if you're using an enum from, for example, a 3rd party library and without access to the source code, you may\nnot be able to use the `@AvroEnumDefault` annotation, in which case you'll need to use the\n`ScalaEnumSchemaFor[E].apply(default: E)` method instead.\n\n#### Defining Enum Defaults for Java Enums\n\nFor java enums such as:\n```java\npublic enum Wine {\n  Malbec, \n  Shiraz, \n  CabSav, \n  Merlot\n}\n```\navro4s gives you two options to define an enum's default value:\n1) You can define an implicit `SchemaFor` using the `JavaEnumSchemaFor[E].apply(default: E)` method\nwhere the method's `default` argument is one of the enum's values or ...\n2) You can use the `@AvroJavaEnumDefault` annotation to declare the default enum value.\n\nFor example, to create an implicit `SchemaFor` for an enum with a default enum value,\nuse the `JavaEnumSchemaFor[E].apply(default: E)` method as follows:\n```scala\nimplicit val schemaForWine: SchemaFor[Wine] = JavaEnumSchemaFor[Wine](default = Wine.Merlot)\n```\nOr, to declare the default enum value, use the `@AvroJavaEnumDefault` annotation as follows:\n```java\npublic enum Wine {\n  Malbec, \n  Shiraz, \n  @AvroJavaEnumDefault CabSav,\n  Merlot\n}\n```\nAvro4s also supports the following java annotations for java enums:\n* `@AvroJavaName`\n* `@AvroJavaNamespace`\n* `@AvroJavaProp`\n\nPutting it all together, you can define a java enum with using avro4s's annotations as follows:\n```java\n@AvroJavaName(\"MyWine\")\n@AvroJavaNamespace(\"my.namespace\")\n@AvroJavaProp(key = \"hello\", value = \"world\")\npublic enum Wine {\n  Malbec, \n  Shiraz, \n  @AvroJavaEnumDefault CabSav,\n  Merlot\n}\n```\nresulting in the following AVRO schema:\n```json\n{\n  \"type\": \"enum\",\n  \"name\": \"MyWine\",\n  \"namespace\": \"my.namespace\",\n  \"symbols\": [\n    \"Malbec\",\n    \"Shiraz\",\n    \"CabSav\",\n    \"Merlot\"\n  ],\n  \"default\": \"CabSav\",\n  \"hello\": \"world\"\n}\n```\n#### Defining Enum Defaults for Sealed Traits\n\nFor sealed traits, you can define the trait's default enum using the `@AvroEnumDefault` annotation as follows:\n```scala\n@AvroEnumDefault(Dog)\nsealed trait Animal\n@AvroSortPriority(0) case object Cat extends Animal\n@AvroSortPriority(-1) case object Dog extends Animal\n```\nresulting in the following AVRO schema:\n```json\n{\n  \"type\" : \"enum\",\n  \"name\" : \"Animal\",\n  \"symbols\" : [ \"Cat\", \"Dog\" ],\n  \"default\" : \"Dog\"\n}\n```\n\n### Avro Fixed\n\nAvro supports the idea of fixed length byte arrays. To use these we can either override the schema generated for a type to return `Schema.Type.Fixed`. This will work for types like String or UUID. You can also annotate a field with @AvroFixed(size).\nFor example:\n\n```scala\npackage com.sksamuel\ncase class Foo(@AvroFixed(7) mystring: String)\nval schema = AvroSchema[Foo]\n```\n\nWill generate the following schema:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"Foo\",\n  \"namespace\": \"com.sksamuel\",\n  \"fields\": [\n    {\n      \"name\": \"mystring\",\n      \"type\": {\n        \"type\": \"fixed\",\n        \"name\": \"mystring\",\n        \"size\": 7\n      }\n    }\n  ]\n}\n```\n\nIf you have a value type that you always want to be represented as fixed, then rather than annotate every single location it is used, you can annotate the value type itself.\n\n```scala\npackage com.sksamuel\n\n@AvroFixed(4)\ncase class FixedA(bytes: Array[Byte]) extends AnyVal\n\ncase class Foo(a: FixedA)\nval schema = AvroSchema[Foo]\n```\n\nAnd this would generate:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"Foo\",\n  \"namespace\": \"com.sksamuel\",\n  \"fields\": [\n    {\n      \"name\": \"a\",\n      \"type\": {\n        \"type\": \"fixed\",\n        \"name\": \"FixedA\",\n        \"size\": 4\n      }\n    }\n  ]\n}\n```\n\nFinally, these annotated value types can be used as top level schemas too:\n\n```scala\npackage com.sksamuel\n\n@AvroFixed(6)\ncase class FixedA(bytes: Array[Byte]) extends AnyVal\nval schema = AvroSchema[FixedA]\n```\n\n```json\n{\n  \"type\": \"fixed\",\n  \"name\": \"FixedA\",\n  \"namespace\": \"com.sksamuel\",\n  \"size\": 6\n}\n```\n\n### Controlling order of types in generated union schemas\n\nThe order of types in a union is significant in Avro, e.g the schemas `type: [\"int\", \"float\"]` and `type: [\"float\", \"int\"]` are different. This can cause problems when generating schemas for sealed trait hierarchies. Ideally we would generate schemas using the source code declaration order of the types. So for example:\n\n```scala\nsealed trait Animal\ncase class Dog(howFriendly: Float) extends Animal\ncase class Fish(remembersYou: Boolean) extends Animal\n```\n\nShould generate a schema where the order of types in the unions is `Dog, Fish`. Unfortunately, the `SchemaFor` macro can sometimes lose track of what the declaration order is - especially with larger hierarchies. In any situation where this is happening you can use the `@AvroSortPriority` annotation to explicitly control what order the types appear in. `@AvroSortPriority` takes a single float argument, which is the priority this field should be treated with, higher priority means closer to the beginning of the union. For example:\n\n```scala\nsealed trait Animal\n@AvroSortPriority(1)\ncase class Dog(howFriendly: Float) extends Animal\n@AvroSortPriority(2)\ncase class Fish(remembersYou: Boolean) extends Animal\n```\n\nWould output the types in the union as `Fish,Dog`.\n\n### Recursive Schemas\n\nAvro4s supports recursive schemas. Customizing them requires some thought, so if you can stick with the out-of-the-box\nprovided schemas and customization via annotations. \n\n### Customizing Recursive Schemas\n\nThe simplest way to customize schemas for recursive types is to provide custom `SchemaFor` instances for all types that\nform the recursion. Given for example the following recursive `Tree` type,\n\n```scala\nsealed trait Tree[+T]\ncase class Branch[+T](left: Tree[T], right: Tree[T]) extends Tree[T]\ncase class Leaf[+T](value: T) extends Tree[T]\n```\n\nit is easy to customize recursive schemas by providing a `SchemaFor` for both `Tree` and `Branch`:\n\n```scala\nimport scala.collection.JavaConverters._\n\nval leafSchema = AvroSchema[Leaf[Int]]\nval branchSchema = Schema.createRecord(\"CustomBranch\", \"custom schema\", \"custom\", false)\nval treeSchema = Schema.createUnion(leafSchema, branchSchema)\nbranchSchema.setFields(Seq(new Schema.Field(\"left\", treeSchema), new Schema.Field(\"right\", treeSchema)).asJava)\n\nval treeSchemaFor: SchemaFor[Tree[Int]] = SchemaFor(treeSchema)\nval branchSchemaFor: SchemaFor[Branch[Int]] = SchemaFor(branchSchema)\n```\n\nIf you want to customize the schema for one type that is part of a type recursion (e.g., `Branch[Int]`) while using\ngenerated schemas, this can be done as follows (sticking with the above example):\n\n```scala\n// 1. Use implicit def here so that this SchemaFor gets summoned for Branch[Int] in steps 6. and 10. below\n// 2. Implement a ResolvableSchemaFor instead of SchemaFor directly so that SchemaFor creation can be deferred\nimplicit def branchSchemaFor: SchemaFor[Branch[Int]] = new ResolvableSchemaFor[Branch[Int]] {\n  def schemaFor(env: DefinitionEnvironment[SchemaFor], update: SchemaUpdate): SchemaFor[Branch[Int]] =\n    // 3. first, check whether SchemaFor[Branch[Int]] is already defined and return that if it is \n    env.get[Branch[Int]].getOrElse {\n      // 4. otherwise, create an incomplete SchemaFor (it initially lacks fields)\n      val record: SchemaFor[Branch[Int]] = SchemaFor(Schema.createRecord(\"CustomBranch\", \"custom schema\", \"custom\", false))\n      // 5. extend the definition environment with the created SchemaFor[Branch[Int]]\n      val nextEnv = env.updated(record)\n      // 6. summon a schema for Tree[Int] (using the Branch[Int] from step 1. through implicits)\n      // 7. resolve the schema to get a finalized schema for Tree[Int]\n      val treeSchema = SchemaFor[Tree[Int]].resolveSchemaFor(nextEnv, NoUpdate).schema\n      // 8. close the reference cycle between Branch[Int] and Tree[Int]\n      val fields = Seq(new Schema.Field(\"left\", treeSchema), new Schema.Field(\"right\", treeSchema))\n      record.schema.setFields(fields.asJava)\n      // 9. return the final SchemaFor[Branch[Int]]\n      record\n    }\n}\n\n// 10. summon Schema for tree and kick off encoder resolution.\nval treeSchema = AvroSchema[Tree[Int]]\n```\n\n## Input / Output\n\n### Serializing\n\nAvro4s allows us to easily serialize case classes using an instance of `AvroOutputStream` which we write to, and close, just like you would any regular output stream.\nAn `AvroOutputStream` can be created from a `File`, `Path`, or by wrapping another `OutputStream`.\nWhen we create one, we specify the type of objects that we will be serializing and provide a writer schema.\nFor example, to serialize instances of our Pizza class:\n\n```scala\nimport java.io.File\nimport com.sksamuel.avro4s.AvroOutputStream\n\nval pepperoni = Pizza(\"pepperoni\", Seq(Ingredient(\"pepperoni\", 12, 4.4), Ingredient(\"onions\", 1, 0.4)), false, false, 598)\nval hawaiian = Pizza(\"hawaiian\", Seq(Ingredient(\"ham\", 1.5, 5.6), Ingredient(\"pineapple\", 5.2, 0.2)), false, false, 391)\n\nval schema = AvroSchema[Pizza]\n\nval os = AvroOutputStream.data[Pizza].to(new File(\"pizzas.avro\")).build()\nos.write(Seq(pepperoni, hawaiian))\nos.flush()\nos.close()\n```\n\n### Deserializing\n\nWe can easily deserialize a file back into case classes.\nGiven the `pizzas.avro` file we generated in the previous section on serialization, we will read this back in using the `AvroInputStream` class.\nWe first create an instance of the input stream specifying the types we will read back, the source file, and then build it using a reader schema.\n\nOnce the input stream is created, we can invoke `iterator` which will return a lazy iterator that reads on demand the data in the file.\n\nIn this example, we'll load all data at once from the iterator via `toSet`.\n\n```scala\nimport com.sksamuel.avro4s.AvroInputStream\n\nval schema = AvroSchema[Pizza]\n\nval is = AvroInputStream.data[Pizza].from(new File(\"pizzas.avro\")).build(schema)\nval pizzas = is.iterator.toSet\nis.close()\n\nprintln(pizzas.mkString(\"\\n\"))\n```\n\nWill print out:\n\n```scala\nPizza(pepperoni,List(Ingredient(pepperoni,12.2,4.4), Ingredient(onions,1.2,0.4)),false,false,500)\nPizza(hawaiian,List(Ingredient(ham,1.5,5.6), Ingredient(pineapple,5.2,0.2)),false,false,500)\n```\n\n### Binary and JSON Formats\n\nYou can serialize as [binary](https://avro.apache.org/docs/1.8.2/spec.html#binary_encoding) or [json](https://avro.apache.org/docs/1.8.2/spec.html#json_encoding)\nby specifying the format when creating the input or output stream. In the earlier example we use `data` which is considered the \"default\" for Avro.\n\nTo use json or binary, you can do the following:\n\n```scala\nAvroOutputStream.binary.to(...).build(...)\nAvroOutputStream.json.to(...).build(...)\n\nAvroInputStream.binary.from(...).build(...)\nAvroInputStream.json.from(...).build(...)\n```\n\nNote: Binary serialization does not include the schema in the output.\n\n## Avro Records\n\nIn Avro there are two container interfaces designed for complex types - `GenericRecord`, which is the most commonly used, along with the lesser used `SpecificRecord`.\nThese record types are used with a schema of type `Schema.Type.RECORD`.\n\nTo interface with the Avro Java API or with third party frameworks like Kafka it is sometimes desirable to convert between your case classes and these records,\nrather than using the input/output streams that avro4s provides.\n\nTo perform conversions, use the `RecordFormat` typeclass which converts to/from case classes and Avro records.\n\nNote: In Avro, `GenericRecord` and `SpecificRecord` don't have a common _Record_ interface (just a `Container` interface which simply provides for a schema without any methods for accessing values), so\navro4s has defined a `Record` trait, which is the union of the `GenericRecord` and `SpecificRecord` interfaces. This allows avro4s to generate records which implement both interfaces at the same time.\n\nTo convert from a class into a record:\n\n```scala\ncase class Composer(name: String, birthplace: String, compositions: Seq[String])\nval ennio = Composer(\"ennio morricone\", \"rome\", Seq(\"legend of 1900\", \"ecstasy of gold\"))\nval schema: Schema = AvroSchema[Composer]\nimplicit val toRecord: ToRecord[Composer] = ToRecord.apply[Composer](schema)\nimplicit val fromRecord: FromRecord[Composer] = FromRecord.apply[Composer](schema)\nval format: RecordFormat[Composer] = RecordFormat.apply[Composer](schema)\n// record is a type that implements both GenericRecord and Specific Record\nval record = format.to(ennio)\n```\n\nAnd to go from a record back into a type:\n\n```scala\n// given some record from earlier\nval record = ...\nval format = RecordFormat[Composer]\nval ennio = format.from(record)\n```\n\n## Usage as a Kafka Serde\n\nThe [com.sksamuel.avro4s.kafka.GenericSerde](avro4s-kafka/src/main/scala/com/sksamuel/avro4s/kafka/GenericSerde.scala) class can be used as a Kafka Serdes to serialize/deserialize case classes into Avro records with Avro4s.\nNote that this class is not integrated with the schema registry.\n\n```scala\n\n  import java.util.Properties\n  import org.apache.kafka.clients.CommonClientConfigs\n  import org.apache.kafka.clients.producer.ProducerConfig\n  import com.sksamuel.avro4s.AvroFormat\n\n  case class TheKafkaKey(id: String)\n  case class TheKafkaValue(name: String, location: String)\n\n  val producerProps = new Properties();\n  producerProps.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, \"...\")\n  producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, new GenericSerde[TheKafkaKey](AvroFormat.Binary))\n  producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, new GenericSerde[TheKafkaValue](AvroFormat.Binary))\n  new ProducerConfig(producerProps)\n```\n\n## Type Mappings\n\nAvro4s defines two typeclasses, `Encoder` and `Decoder` which do the work\nof mapping between scala values and Avro compatible values. Avro has no understanding of Scala types, or anything outside of it's built in set of supported types, so all values must be converted to something that is compatible with Avro. There are built in encoders and decoders for all the common JDK and Scala SDK types, including macro generated instances for case classes.\n\nFor example a `java.sql.Timestamp` is usually encoded as a Long, and a `java.util.UUID` is encoded as a String.\n\nDecoders do the same work, but in reverse. They take an Avro value, such as null and return a scala value, such as `Option`.\n\nSome values can be mapped in multiple ways depending on how the schema was generated. For example a String, which is usually encoded as \n`org.apache.avro.util.Utf8` could also be encoded as an array of bytes if the generated schema for that field was `Schema.Type.BYTES`. Therefore some encoders will take into account the schema passed to them when choosing the avro compatible type. In the schemas section you saw how you could influence which schema is generated for types.\n\n### Built in Type Mappings\n\n``` scala\nimport scala.collection.{Array, List, Seq, Iterable, Set, Map, Option, Either}\nimport shapeless.{:+:, CNil}\n```\n\nThe following table shows how types used in your code will be mapped / encoded in the generated Avro schemas and files.\nIf a type can be mapped in multiple ways, it is listed more than once.\n\n| Scala Type                   \t         | Schema Type   \t    | Logical Type     \t| Encoded Type                      |\n|----------------------------------------|--------------------|------------------\t|-----------------------------------|\n| String                       \t         | STRING        \t    |                  \t| Utf8                              |\n| String                       \t         | FIXED        \t     |                  \t| GenericFixed                      |\n| String                       \t         | BYTES        \t     |                  \t| ByteBuffer                        |\n| Boolean                      \t         | BOOLEAN       \t    |                  \t| java.lang.Boolean                 |\n| Long                         \t         | LONG          \t    |                  \t| java.lang.Long                    |\n| Int                          \t         | INT           \t    |                  \t| java.lang.Integer                 |\n| Short                        \t         | INT           \t    |                  \t| java.lang.Integer                 |\n| Byte                         \t         | INT           \t    |                  \t| java.lang.Integer                 |\n| Double                       \t         | DOUBLE        \t    |                  \t| java.lang.Double                  |\n| Float                        \t         | FLOAT         \t    |                  \t| java.lang.Float                   |\n| UUID                         \t         | STRING        \t    | UUID             \t| Utf8                              |\n| LocalDate                    \t         | INT           \t    | Date             \t| java.lang.Int                     |\n| LocalTime                    \t         | INT           \t    | time-millis      \t| java.lang.Int                     |\n| LocalDateTime                \t         | LONG          \t    | timestamp-nanos \t| java.lang.Long                    |\n| java.sql.Date                \t         | INT           \t    | Date             \t| java.lang.Int                     |\n| Instant                      \t         | LONG          \t    | Timestamp-Millis \t| java.lang.Long                    |\n| Timestamp                    \t         | LONG          \t    | Timestamp-Millis \t| java.lang.Long                    |\n| BigDecimal                   \t         | BYTES         \t    | Decimal\u003c8,2\u003e     \t| ByteBuffer                        |\n| BigDecimal                   \t         | FIXED         \t    | Decimal\u003c8,2\u003e     \t| GenericFixed                      |\n| BigDecimal                   \t         | STRING         \t   | Decimal\u003c8,2\u003e     \t| String                            |\n| Option[T]                    \t         | UNION\u003cnull,T\u003e \t    |                  \t| null, T                           |\n| Array[Byte]                  \t         | BYTES         \t    |                  \t| ByteBuffer                        |\n| Array[Byte]                  \t         | FIXED         \t    |                  \t| GenericFixed                      |\n| ByteBuffer                   \t         | BYTES         \t    |                  \t| ByteBuffer                        |\n| Seq[Byte]                    \t         | BYTES         \t    |                  \t| ByteBuffer                        |\n| List[Byte]                   \t         | BYTES         \t    |                  \t| ByteBuffer                        |\n| Vector[Byte]                 \t         | BYTES         \t    |                  \t| ByteBuffer                        |\n| Array[T]                     \t         | ARRAY\u003cT\u003e      \t    |                  \t| Array[T]                          |\n| Vector[T]                    \t         | ARRAY\u003cT\u003e      \t    |                  \t| Array[T]                          |\n| Seq[T]                       \t         | ARRAY\u003cT\u003e      \t    |                  \t| Array[T]                          |\n| List[T]                      \t         | ARRAY\u003cT\u003e      \t    |                  \t| Array[T]                          |\n| Set[T]                       \t         | ARRAY\u003cT\u003e      \t    |                  \t| Array[T]                          |\n| sealed trait of case classes \t         | UNION\u003cA,B,..\u003e  \t   |                  \t| A, B, ...                         |\n| sealed trait of case objects \t         | ENUM\u003cA,B,..\u003e  \t    |                  \t| GenericEnumSymbol                 |\n| Map[String, V]              \t          | MAP\u003cV\u003e        \t    |                  \t| java.util.Map[String, V]          |\n| Either[A,B]                  \t         | UNION\u003cA,B\u003e    \t    |                  \t| A, B                              |\n| A :+: B :+: C :+: CNil       \t         | UNION\u003cA,B,C\u003e  \t    |                  \t| A, B, ...                         |\n| case class T                 \t         | RECORD        \t    |                  \t| GenericRecord with SpecificRecord |\n| Scala enumeration            \t         | ENUM          \t    |                  \t| GenericEnumSymbol                 |\n| Java enumeration             \t         | ENUM          \t    |                  \t| GenericEnumSymbol                 |\n| Scala tuples                           | RECORD             |                   | GenericRecord with SpecificRecord |\n| Option[Either[A,B]]                    | UNION\u003cnull,A,B\u003e    |                   | null, A, B                        |\n| option of sealed trait of case classes | UNION\u003cnull,A,B,..\u003e |                   | null, A, B, ...                   |\n| option of sealed trait of case objects   | UNION\u003cnull,A,B,..\u003e |                   | null, GenericEnumSymbol           |\n\nTo select the encoding in case multiple encoded types exist, create a new `Encoder` with a corresponding `SchemaFor` \ninstance to the via `withSchema`. For example, creating a string encoder that uses target type `BYTES` works like this:\n\n```scala\nval stringSchemaFor = SchemaFor[String](Schema.create(Schema.Type.BYTES))\nval stringEncoder = Encoder[String].withSchema(stringSchemaFor)\n``` \n\n### Custom Type Mappings\n\nIt is very easy to add custom type mappings. To do this, we bring into scope a custom implicit of `Encoder[T]` and/or `Decoder[T]`.\n\nFor example, to create a custom type mapping for a type Foo which writes out the contents in upper case, but always reads\nthe contents in lower case, we can do the following:\n\n```scala\ncase class Foo(a: String, b: String)\n\nimplicit object FooEncoder extends Encoder[Foo] {\n\n  override val schemaFor = SchemaFor[Foo]\n\n  override def encode(foo: Foo) = {\n    val record = new GenericData.Record(schema)\n    record.put(\"a\", foo.a.toUpperCase)\n    record.put(\"b\", foo.b.toUpperCase)\n    record\n  }\n}\n\nimplicit object FooDecoder extends Decoder[Foo] {\n\n  override val schemaFor = SchemaFor[Foo]\n\n  override def decode(value: Any) = {\n    val record = value.asInstanceOf[GenericRecord]\n    Foo(record.get(\"a\").toString.toLowerCase, record.get(\"b\").toString.toLowerCase)\n  }\n}\n```\n\nAnother example is changing the way we serialize `LocalDateTime` to store these dates as ISO strings. In this case, we are\nwriting out a String rather than the default Long so we must also change the schema type. Therefore, we must add an implicit `SchemaFor` as well as the encoders\nand decoders.\n\n```scala\nimplicit val LocalDateTimeSchemaFor = SchemaFor[LocalDateTime](Schema.create(Schema.Type.STRING))\n\nimplicit object DateTimeEncoder extends Encoder[LocalDateTime] {\n\n  override val schemaFor = LocalDateTimeSchemaFor\n\n  override def encode(value: LocalDateTime) = \n    ISODateTimeFormat.dateTime().print(value)\n}\n\nimplicit object DateTimeDecoder extends Decoder[LocalDateTime] {\n\n  override val schemaFor = LocalDateTimeSchemaFor\n\n  override def decode(value: Any) = \n    ISODateTimeFormat.dateTime().parseDateTime(value.toString)\n}\n```\n\nThese typeclasses must be implicit and in scope when you use `AvroSchema` or `RecordFormat`.\n\n### Coproducts\n\nAvro supports generalised unions, eithers of more than two values.\nTo represent these in scala, we use `shapeless.:+:`, such that `A :+: B :+: C :+: CNil` represents cases where a type is `A` OR `B` OR `C`.\nSee shapeless' [documentation on coproducts](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#coproducts-and-discriminated-unions) for more on how to use coproducts.\n\n### Sealed hierarchies\n\nScala sealed traits/classes are supported both when it comes to schema generation and conversions to/from `GenericRecord`.\nGenerally sealed hierarchies are encoded as unions - in the same way like Coproducts.\nUnder the hood, shapeless `Generic` is used to derive Coproduct representation for sealed hierarchy.\n\nWhen all descendants of sealed trait/class are singleton objects, optimized, enum-based encoding is used instead.\n\n\n## Decimal scale, precision and rounding mode\n\nIn order to customize the scale and precision used by `BigDecimal` schema generators, bring an implicit `ScalePrecision` instance into scope.before using `AvroSchema`.\n\n```scala\nimport com.sksamuel.avro4s.ScalePrecision\n\ncase class MyDecimal(d: BigDecimal)\n\nimplicit val sp = ScalePrecision(4, 20)\nval schema = AvroSchema[MyDecimal]\n```\n\n```json\n{\n  \"type\":\"record\",\n  \"name\":\"MyDecimal\",\n  \"namespace\":\"com.foo\",\n  \"fields\":[{\n    \"name\":\"d\",\n    \"type\":{\n      \"type\":\"bytes\",\n      \"logicalType\":\"decimal\",\n      \"scale\":\"4\",\n      \"precision\":\"20\"\n    }\n  }]\n}\n```\n\nWhen encoding values, it may be necessary to round values if they need to be converted to the scale used by the schema. By default this is `RoundingMode.UNNECESSARY` which will throw an exception if rounding is required.\nIn order to change this, add an implicit `RoundingMode` value before the `Encoder` is generated.\n\n```scala\ncase class MyDecimal(d: BigDecimal)\n\nimplicit val sp = ScalePrecision(4, 20)\nval schema = AvroSchema[MyDecimal]\n\nimplicit val roundingMode = RoundingMode.HALF_UP\nval encoder = Encoder[MyDecimal]\n``` \n\n## Type Parameters\n\nWhen serializing a class with one or more type parameters, the avro name used in a schema is the name of the raw type, plus the actual type parameters. In other words, it would be of the form `rawtype__typeparam1_typeparam2_..._typeparamN`. So for example, the schema for a type `Event[Foo]` would have the avro name `event__foo`.\n\nYou can disable this by annotating the class with `@AvroErasedName` which uses the JVM erased name - in other words, it drops type parameter information. So the aforementioned `Event[Foo]` would be simply `event`.\n\n## Selective Customisation\n\nYou can selectively customise the way Avro4s generates certain parts of your hierarchy, thanks to implicit precedence. Suppose you have the following classes:\n\n```scala\ncase class Product(name: String, price: Price, litres: BigDecimal)\ncase class Price(currency: String, amount: BigDecimal)\n```\n\nAnd you want to selectively use different scale/precision for the `price` and `litres` quantities. You can do this by forcing the implicits in the corresponding companion objects.\n\n``` scala\nobject Price {\n  implicit val sp = ScalePrecision(10, 2)\n  implicit val schema = SchemaFor[Price]\n}\n\nobject Product {\n  implicit val sp = ScalePrecision(8, 4)\n  implicit val schema = SchemaFor[Product]\n}\n```\n\nThis will result in a schema where both `BigDecimal` quantities have their own separate scale and precision.\n\n## Cats Support\n\nIf you use cats in your domain objects, then Avro4s provides a cats module with schemas, encoders and decoders for some cats types.\nJust import `import com.sksamuel.avro4s.cats._` before calling into the macros.\n\n```scala\ncase class Foo(list: NonEmptyList[String], vector: NonEmptyVector[Boolean])\nval schema = AvroSchema[Foo]\n```\n\n## Refined Support\n\nIf you use [refined](https://github.com/fthomas/refined) in your domain objects, then Avro4s provides a refined module with schemas, encoders and decoders for refined types.\nJust import `import com.sksamuel.avro4s.refined._` before calling into the macros.\n\n```scala\ncase class Foo(nonEmptyStr: String Refined NonEmpty)\nval schema = AvroSchema[Foo]\n```\n\n### Mapping Recursive Types\n\nAvro4s supports encoders and decoders for recursive types. Customizing them is possible, but involved. As with customizing\nSchemaFor instances for recursive types, the simplest way to customize encoders and decoders is to provide a custom\nencoder and decoder for all types that form the recursion.\n\nIf that isn't possible, you can customize encoders / decoders for one single type and participate in creating a cyclic\ngraph of encoders / decoders. To give an example, consider the following recursive type for trees.\n\n```scala\nsealed trait Tree[+T]\ncase class Branch[+T](left: Tree[T], right: Tree[T]) extends Tree[T]\ncase class Leaf[+T](value: T) extends Tree[T]\n```\n\nFor this, a custom `Branch[Int]` encoder can be defined as follows.\n\n```scala\n// 1. use implicit def so that Encoder.apply[Tree[Int]] in step 7. and 10. below picks this resolvable encoder for branches.\n// 2. implement a ResolvableEncoder instead of Encoder directly so that encoder creation can be deferred\nimplicit def branchEncoder: Encoder[Branch[Int]] = new ResolvableEncoder[Branch[Int]] {\n\ndef encoder(env: DefinitionEnvironment[Encoder], update: SchemaUpdate): Encoder[Branch[Int]] =\n  // 3. lookup in the definition environment whether we already have created an encoder for branch.\n  env.get[Branch[Int]].getOrElse {\n\n    // 4. use var here to first create an acyclic graph and close it later.\n    var treeEncoder: Encoder[Tree[Int]] = null\n\n    // 5. create a partially initialized encoder for branches (it lacks a value for treeEncoder on creation).\n    val encoder = new Encoder[Branch[Int]] {\n      val schemaFor: SchemaFor[Branch[Int]] = SchemaFor[Branch[Int]]\n\n      def encode(value: Branch[Int]): AnyRef =\n        ImmutableRecord(schema, Seq(treeEncoder.encode(value.left), treeEncoder.encode(value.right)))\n    }\n\n    // 6. extend the definition environment with the newly created encoder so that subsequent lookups (step 3.) can return it\n    val nextEnv = env.updated(encoder)\n\n    // 7. resolve the tree encoder with the extended environment; the extended env will be passed back to the lookup\n    //    performed in step 3. above.\n    // 9. complete the initialization by closing the reference cycle: the branch encoder and tree encoder now \n    //    reference each other.\n    treeEncoder = Encoder.apply[Tree[Int]].resolveEncoder(nextEnv, NoUpdate)\n    encoder\n  }\n}\n\n// 10. summon encoder for tree and kick off encoder resolution.\nval toRecord = ToRecord[Tree[Int]]\n```\n\nWhy is this so complicated? Glad you asked! Turns out that caring for performance, providing customization via \nannotations, and using Magnolia for automatic typeclass derivation (which is great in itself) are three constraints \nthat aren't easy to combine. This design is the best we came up with; if you have a better design for this, please \ncontribute it!\n\n## Using avro4s in your project\n\n#### Gradle\n\n`compile 'com.sksamuel.avro4s:avro4s-core_2.12:xxx'`\n\n#### SBT\n\n`libraryDependencies += \"com.sksamuel.avro4s\" %% \"avro4s-core\" % \"xxx\"`\n\n#### Maven\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.sksamuel.avro4s\u003c/groupId\u003e\n    \u003cartifactId\u003eavro4s-core_2.12\u003c/artifactId\u003e\n    \u003cversion\u003exxx\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nCheck the latest released version on [Maven Central](http://search.maven.org/#search|ga|1|g%3A%22com.sksamuel.avro4s%22)\n\n## Contributions\nContributions to avro4s are always welcome. Good ways to contribute include:\n\n* Raising bugs and feature requests\n* Fixing bugs and enhancing the DSL\n* Improving the performance of avro4s\n* Adding to the documentation\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsksamuel%2Favro4s","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsksamuel%2Favro4s","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsksamuel%2Favro4s/lists"}