{"id":47737773,"url":"https://github.com/twmb/avro","last_synced_at":"2026-04-02T23:01:31.001Z","repository":{"id":341135139,"uuid":"1169048744","full_name":"twmb/avro","owner":"twmb","description":"Avro in Go, fast.","archived":false,"fork":false,"pushed_at":"2026-03-27T18:55:20.000Z","size":299,"stargazers_count":9,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-28T02:26:32.710Z","etag":null,"topics":["avro","go","golang"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twmb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-28T05:22:46.000Z","updated_at":"2026-03-27T18:53:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/twmb/avro","commit_stats":null,"previous_names":["twmb/avro"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/twmb/avro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twmb%2Favro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twmb%2Favro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twmb%2Favro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twmb%2Favro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twmb","download_url":"https://codeload.github.com/twmb/avro/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twmb%2Favro/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31318133,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T21:35:00.834Z","status":"ssl_error","status_checked_at":"2026-04-02T21:34:59.806Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","go","golang"],"created_at":"2026-04-02T23:01:29.877Z","updated_at":"2026-04-02T23:01:30.845Z","avatar_url":"https://github.com/twmb.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# avro\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/twmb/avro.svg)](https://pkg.go.dev/github.com/twmb/avro)\n\nEncode and decode [Avro](https://avro.apache.org/docs/current/specification/) binary data.\n\nParse an Avro JSON schema, then encode and decode Go values directly — no\ncode generation required. Supports all primitive and complex types, logical\ntypes, schema evolution, Object Container Files, Single Object Encoding, and\nfingerprinting.\n\n## Index\n\n- [Quick Start](#quick-start)\n- [Type Mapping](#type-mapping)\n- [Struct Tags](#struct-tags)\n- [Schema Inference](#schema-inference)\n- [Schema Introspection](#schema-introspection)\n- [Logical Types](#logical-types)\n- [Schema Evolution](#schema-evolution)\n- [Schema Cache](#schema-cache)\n- [Custom Types](#custom-types)\n- [Object Container Files](#object-container-files)\n- [JSON Encoding](#json-encoding)\n- [Single Object Encoding](#single-object-encoding)\n- [Fingerprinting](#fingerprinting)\n- [Performance](#performance)\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/twmb/avro\"\n)\n\nvar schema = avro.MustParse(`{\n    \"type\": \"record\",\n    \"name\": \"User\",\n    \"fields\": [\n        {\"name\": \"name\", \"type\": \"string\"},\n        {\"name\": \"age\",  \"type\": \"int\"}\n    ]\n}`)\n\ntype User struct {\n\tName string `avro:\"name\"`\n\tAge  int    `avro:\"age\"`\n}\n\nfunc main() {\n\t// Encode\n\tdata, err := schema.Encode(\u0026User{Name: \"Alice\", Age: 30})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Decode\n\tvar u User\n\t_, err = schema.Decode(data, \u0026u)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Println(u) // {Alice 30}\n}\n```\n\n`Parse` accepts options: pass `WithLaxNames()` to allow non-standard characters\nin type and field names (useful for interop with schemas from other languages).\n\n## Type Mapping\n\nThe table below shows which Go types can be used with each Avro type.\n\n| Avro Type | Encode | Decode |\n|-----------|--------|--------|\n| null      | `any` (nil) | `any` |\n| boolean   | `bool` | `bool`, `any` |\n| int, long | `int`, `int8`–`int64`, `uint`–`uint64`, `float64`, `json.Number` | `int`, `int8`–`int64`, `uint`–`uint64`, `any` |\n| float     | `float32`, `float64`, `json.Number` | `float32`, `float64`, `any` |\n| double    | `float64`, `float32`, `json.Number` | `float64`, `float32`, `any` |\n| string    | `string`, `[]byte`, `encoding.TextAppender`, `encoding.TextMarshaler` | `string`, `[]byte`, `encoding.TextUnmarshaler`, `any` |\n| bytes     | `[]byte`, `string` | `[]byte`, `string`, `any` |\n| enum      | `string`, any integer type (ordinal) | `string`, any integer type (ordinal), `any` |\n| fixed     | `[N]byte`, `[]byte` | `[N]byte`, `[]byte`, `any` |\n| array     | slice | slice, `any` |\n| map       | `map[string]T` | `map[string]T`, `any` |\n| union     | `any`, `*T`, or the matched branch type | `any`, `*T`, or the matched branch type |\n| record    | struct, `map[string]any` | struct, `map[string]any`, `any` |\n\nWhen decoding into `any`, values use their natural Go types: `nil`, `bool`,\n`int32`, `int64`, `float32`, `float64`, `string`, `[]byte`, `[]any`,\n`map[string]any`. Logical types use `time.Time` (UTC) for timestamps and\ndates, `time.Duration` for time-of-day types, `json.Number` for decimals,\nand `avro.Duration` for the duration logical type.\n\nEncoding also accepts `json.Number` for any numeric type (supporting\n`json.Decoder.UseNumber()` pipelines) and `[]byte` for string fields (and\nvice versa).\n\n## Struct Tags\n\nStruct fields are matched to Avro record fields by name. Use the `avro` struct\ntag to control the mapping:\n\n```go\ntype Example struct {\n    Name    string  `avro:\"name\"`          // maps to Avro field \"name\"\n    Ignored int     `avro:\"-\"`             // excluded from encoding/decoding\n    Inner   Nested  `avro:\",inline\"`       // inline Nested's fields into this record\n    Value   int     `avro:\"val,omitzero\"`  // encode zero value as Avro default\n}\n```\n\nThe tag format is:\n\n```\navro:\"[name][,option][,option]...\"\n```\n\nThe name portion maps the struct field to the Avro field with that name. If\nempty, the Go field name is used as-is. A tag of `\"-\"` excludes the field\nentirely.\n\nSupported options:\n\n- **inline**: flatten a nested struct's fields into the parent record, as if\n  they were declared directly on the parent. The field must be a struct or\n  pointer to struct. This works like anonymous (embedded) struct fields, but\n  for named fields. When using inline, the name portion of the tag must be\n  empty.\n\n- **omitzero**: when encoding, if the field is the zero value for its type (or\n  implements an `IsZero() bool` method that returns true), the Avro default\n  value from the schema is used instead. This is useful for optional fields in\n  `[\"null\", T]` unions or fields with explicit defaults.\n\nEmbedded (anonymous) struct fields are automatically inlined — their fields are\npromoted into the parent as if declared directly. To prevent inlining an\nembedded struct, give it an explicit name tag:\n\n```go\ntype Parent struct {\n    Nested                    // inlined: Nested's fields are promoted\n    Other  Aux `avro:\"other\"` // not inlined: treated as a single field\n}\n```\n\nWhen multiple fields at different depths resolve to the same Avro field name,\nthe shallowest field wins. Among fields at the same depth, a tagged field wins\nover an untagged one.\n\n## Schema Inference\n\n`SchemaFor` infers an Avro schema from a Go struct type, using the same struct\ntags as encoding/decoding:\n\n```go\ntype User struct {\n    Name      string     `avro:\"name\"`\n    Age       int32      `avro:\"age,default=18\"`\n    Email     *string    `avro:\"email\"`\n    CreatedAt time.Time  `avro:\"created_at\"`\n}\n\nschema := avro.MustSchemaFor[User](avro.WithNamespace(\"com.example\"))\n```\n\nThis produces the equivalent of:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"User\",\n  \"namespace\": \"com.example\",\n  \"fields\": [\n    {\"name\": \"name\", \"type\": \"string\"},\n    {\"name\": \"age\", \"type\": \"int\", \"default\": 18},\n    {\"name\": \"email\", \"type\": [\"null\", \"string\"]},\n    {\"name\": \"created_at\", \"type\": {\"type\": \"long\", \"logicalType\": \"timestamp-millis\"}}\n  ]\n}\n```\n\nGo types map to Avro types automatically: `*T` becomes a `[\"null\", T]` union,\n`time.Time` becomes `timestamp-millis`, and so on (see [Type Mapping](#type-mapping)).\n\nAdditional tag options for schema inference:\n\n| Tag | Example | Description |\n|-----|---------|-------------|\n| `default=` | `avro:\",default=0\"` | Default value (must be last; scalars only) |\n| `alias=` | `avro:\",alias=old\"` | Field alias for schema evolution (repeatable) |\n| `timestamp-micros` | `avro:\",timestamp-micros\"` | Override logical type |\n| `decimal(p,s)` | `avro:\",decimal(10,2)\"` | Decimal logical type (required for `*big.Rat`) |\n| `uuid` | `avro:\",uuid\"` | UUID logical type |\n| `date` | `avro:\",date\"` | Date logical type |\n\nOptions:\n\n- `WithNamespace(ns)` sets the Avro namespace for the record.\n- `WithName(name)` overrides the record name (defaults to the Go struct name).\n\n## Schema Introspection\n\n`Schema.Root()` returns a `SchemaNode` representing the parsed schema. This\nprovides read access to all schema metadata including field types, logical\ntypes, doc strings, and custom properties:\n\n```go\nschema, _ := avro.Parse(schemaJSON)\nroot := schema.Root()\n\nfor _, f := range root.Fields {\n    fmt.Printf(\"field %s: type=%s\\n\", f.Name, f.Type.Type)\n    if cn, ok := f.Props[\"connect.name\"].(string); ok {\n        fmt.Printf(\"  kafka connect type: %s\\n\", cn)\n    }\n}\n```\n\n`SchemaNode` can also be used to build schemas programmatically:\n\n```go\nnode := \u0026avro.SchemaNode{\n    Type: \"record\",\n    Name: \"User\",\n    Fields: []avro.SchemaField{\n        {Name: \"name\", Type: avro.SchemaNode{Type: \"string\"}},\n        {Name: \"age\", Type: avro.SchemaNode{Type: \"int\"}, Default: 18},\n    },\n}\nschema, err := node.Schema()\n```\n\n## Logical Types\n\nLogical types decode to their natural Go equivalents:\n\n| Logical Type | Avro Type | Encode | Decode |\n|---|---|---|---|\n| date | int | time.Time, RFC 3339 or YYYY-MM-DD string, or int | time.Time (UTC) |\n| time-millis | int | time.Duration or int | time.Duration |\n| time-micros | long | time.Duration or int | time.Duration |\n| timestamp-millis | long | time.Time, RFC 3339 string, or int | time.Time (UTC) |\n| timestamp-micros | long | time.Time, RFC 3339 string, or int | time.Time (UTC) |\n| timestamp-nanos | long | time.Time, RFC 3339 string, or int | time.Time (UTC) |\n| local-timestamp-millis | long | time.Time, RFC 3339 string, or int | time.Time (UTC) |\n| local-timestamp-micros | long | time.Time, RFC 3339 string, or int | time.Time (UTC) |\n| local-timestamp-nanos | long | time.Time, RFC 3339 string, or int | time.Time (UTC) |\n| uuid | string or fixed(16) | [16]byte or string | [16]byte (typed target) or string (any target) |\n| decimal | bytes or fixed | *big.Rat, float64, numeric string, json.Number, or underlying type | *big.Rat, json.Number, or underlying type |\n| duration | fixed(12) | avro.Duration or underlying type | avro.Duration or underlying type |\n\nWhen encoding, timestamp and date fields accept RFC 3339 strings, and decimal\nfields accept float64 and numeric strings (e.g. \"3.14\"). Values that don't\nmatch the expected format fall through to the underlying type's encoder, which\nwill return an error.\n\nUnknown logical types are silently ignored per the Avro spec, and the\nunderlying type is used as-is.\n\n## Schema Evolution\n\nAvro data is always written with a specific schema — the **writer schema**.\nWhen you read that data later, your application may expect a different schema —\nthe **reader schema**. You may have added a field, removed one, or widened a\ntype from int to long.\n\n`Resolve` bridges this gap. Given the writer and reader schemas, it returns a\nnew schema that decodes data in the old wire format and produces values in the\nreader's layout:\n\n- Fields in the reader but not the writer are filled from **defaults**.\n- Fields in the writer but not the reader are **skipped**.\n- Fields that exist in both are matched by **name** (or **alias**) and decoded,\n  with type promotion applied where needed (e.g. int → long).\n\n### Example\n\nSuppose v1 of your application wrote User records with just a name:\n\n```go\nvar writerSchema = avro.MustParse(`{\n    \"type\": \"record\", \"name\": \"User\",\n    \"fields\": [\n        {\"name\": \"name\", \"type\": \"string\"}\n    ]\n}`)\n```\n\nIn v2 you added an email field with a default:\n\n```go\nvar readerSchema = avro.MustParse(`{\n    \"type\": \"record\", \"name\": \"User\",\n    \"fields\": [\n        {\"name\": \"name\",  \"type\": \"string\"},\n        {\"name\": \"email\", \"type\": \"string\", \"default\": \"\"}\n    ]\n}`)\n\ntype User struct {\n    Name  string `avro:\"name\"`\n    Email string `avro:\"email\"`\n}\n```\n\nTo read old v1 data with your v2 struct, resolve the two schemas:\n\n```go\nresolved, err := avro.Resolve(writerSchema, readerSchema)\n\nvar u User\n_, err = resolved.Decode(v1Data, \u0026u)\n// u == User{Name: \"Alice\", Email: \"\"}\n```\n\nThe following type promotions are supported:\n\n| Writer → Reader |\n|---|\n| int → long, float, double |\n| long → float, double |\n| float → double |\n| string ↔ bytes |\n\n`CheckCompatibility` checks whether two schemas are compatible without\nbuilding a resolved schema. The direction you check depends on the guarantee\nyou need:\n\n```go\n// Backward: new schema can read old data.\navro.CheckCompatibility(oldSchema, newSchema)\n\n// Forward: old schema can read new data.\navro.CheckCompatibility(newSchema, oldSchema)\n\n// Full: check both directions.\navro.CheckCompatibility(oldSchema, newSchema)\navro.CheckCompatibility(newSchema, oldSchema)\n```\n\n## Schema Cache\n\nWhen working with a schema registry, schemas often reference types defined in\nother schemas. `SchemaCache` accumulates named types across multiple Parse\ncalls so they can be resolved:\n\n```go\nvar cache avro.SchemaCache\n\n// Parse referenced schema first — order matters.\n_, err := cache.Parse(`{\n    \"type\": \"record\",\n    \"name\": \"Address\",\n    \"fields\": [{\"name\": \"city\", \"type\": \"string\"}]\n}`)\n\n// Now parse a schema that references Address.\nschema, err := cache.Parse(`{\n    \"type\": \"record\",\n    \"name\": \"User\",\n    \"fields\": [\n        {\"name\": \"name\",    \"type\": \"string\"},\n        {\"name\": \"address\", \"type\": \"Address\"}\n    ]\n}`)\n```\n\nParsing the same schema string multiple times returns the cached result,\nhandling diamond dependencies without caller-side deduplication. The returned\n`*Schema` is independent of the cache and safe to use concurrently.\n\n## Custom Types\n\nRegister custom Go type conversions with `NewCustomType` for type-safe\nprimitive conversions, or `CustomType` for advanced cases:\n\n```go\ntype Money struct {\n    Cents    int64\n    Currency string\n}\n\nmoneyType := avro.NewCustomType[Money, int64](\"money\",\n    func(m Money, _ *avro.SchemaNode) (int64, error) { return m.Cents, nil },\n    func(c int64, _ *avro.SchemaNode) (Money, error) {\n        return Money{Cents: c, Currency: \"USD\"}, nil\n    },\n)\n\nschema := avro.MustParse(moneySchema, moneyType)\n\n// Encode and decode — Money fields are automatically converted.\ndata, _ := schema.Encode(\u0026order)\nvar out Order\nschema.Decode(data, \u0026out) // out.Price is Money{Cents: 500, ...}\n\n// Works with SchemaFor too.\nschema = avro.MustSchemaFor[Order](moneyType)\n```\n\nA matching custom type replaces the built-in logical type deserializer.\nDecode callbacks receive raw Avro-native values (int64 for long, int32\nfor int, etc.). A nil Decode suppresses the built-in handler with zero\noverhead, producing raw values directly:\n\n```go\n// Decode timestamps as raw int64 instead of time.Time.\nschema := avro.MustParse(raw, avro.CustomType{\n    LogicalType: \"timestamp-millis\",\n    AvroType:    \"long\",\n})\n```\n\nFor property-based dispatch (e.g., Kafka Connect / Debezium types), use\nan empty matching criteria with `ErrSkipCustomType`:\n\n```go\navro.CustomType{\n    Decode: func(v any, node *avro.SchemaNode) (any, error) {\n        name, _ := node.Props[\"connect.name\"].(string)\n        switch name {\n        case \"io.debezium.time.Timestamp\":\n            return time.UnixMilli(v.(int64)).UTC(), nil\n        default:\n            return nil, avro.ErrSkipCustomType\n        }\n    },\n}\n```\n\n## Object Container Files\n\nThe `ocf` sub-package reads and writes [Avro Object Container Files](https://avro.apache.org/docs/current/specification/#object-container-files) —\nself-describing binary files that embed the schema in the header and store\ndata in compressed blocks.\n\n### Writing\n\n```go\nvar schema = avro.MustParse(`{\n    \"type\": \"record\",\n    \"name\": \"User\",\n    \"fields\": [\n        {\"name\": \"name\", \"type\": \"string\"},\n        {\"name\": \"age\",  \"type\": \"int\"}\n    ]\n}`)\n\nf, _ := os.Create(\"users.avro\")\nw, err := ocf.NewWriter(f, schema, ocf.WithCodec(ocf.SnappyCodec()))\nif err != nil {\n    log.Fatal(err)\n}\nw.Encode(\u0026User{Name: \"Alice\", Age: 30})\nw.Encode(\u0026User{Name: \"Bob\", Age: 25})\nw.Close()\nf.Close()\n```\n\n### Reading\n\n```go\nf, _ := os.Open(\"users.avro\")\nr, err := ocf.NewReader(f)\nif err != nil {\n    log.Fatal(err)\n}\ndefer r.Close()\nfor {\n    var u User\n    err := r.Decode(\u0026u)\n    if err == io.EOF {\n        break\n    }\n    if err != nil {\n        log.Fatal(err)\n    }\n    fmt.Println(u)\n}\n```\n\nThe reader's `Schema()` method returns the schema parsed from the file header,\nwhich you can pass as the writer schema to `Resolve`.\n\n### Codecs\n\nBuilt-in codecs: **null** (default, no compression), **deflate**\n(`DeflateCodec`), **snappy** (`SnappyCodec`), and **zstandard** (`ZstdCodec`).\nCustom codecs can be provided via the `Codec` interface.\n\n### Appending\n\n`NewAppendWriter` opens an existing OCF for appending — it reads the header to\nrecover the schema, codec, and sync marker, then seeks to the end.\n\n## JSON Encoding\n\n`EncodeJSON` is a schema-aware JSON serializer. By default it produces standard\nJSON with bare union values and `\\uXXXX`-encoded bytes:\n\n```go\n// Standard JSON (default): bare unions\njsonBytes, err := schema.EncodeJSON(\u0026user)\n// {\"name\":\"Alice\",\"email\":\"a@b.com\"}\n\n// Avro JSON: unions wrapped as {\"type_name\": value}\njsonBytes, err = schema.EncodeJSON(\u0026user, avro.TaggedUnions())\n// {\"name\":\"Alice\",\"email\":{\"string\":\"a@b.com\"}}\n```\n\n`DecodeJSON` accepts both formats (tagged and bare unions) and all NaN/Infinity\nconventions:\n\n```go\nvar user User\nerr = schema.DecodeJSON(jsonBytes, \u0026user)\n```\n\n`Decode` and `DecodeJSON` also accept `TaggedUnions()` to wrap union values\nwhen decoding into `*any`:\n\n```go\nvar native any\nschema.Decode(binary, \u0026native, avro.TaggedUnions())\n// native[\"email\"] is map[string]any{\"string\": \"a@b.com\"}\n```\n\n`Encode` and `DecodeJSON` accept both tagged and bare union input, so\ntagged union output from `Decode` can round-trip through `Encode` directly.\n\nPass `TagLogicalTypes()` with `TaggedUnions()` to qualify union branch names\nwith their logical type (e.g. `\"long.timestamp-millis\"` instead of `\"long\"`),\nmatching the linkedin/goavro naming convention.\n\nNaN and Infinity float values are encoded as `\"NaN\"`, `\"Infinity\"`, `\"-Infinity\"`\nstrings by default (Java Avro convention). Pass `LinkedinFloats()` for\nthe linkedin/goavro convention (`null` for NaN, `±1e999` for Infinity).\n\n## Single Object Encoding\n\nFor sending self-describing values over the wire (as opposed to files, where\nOCF is preferred), use Single Object Encoding. Each message is a 2-byte magic\nheader, an 8-byte CRC-64-AVRO fingerprint, and the Avro binary payload.\n\n```go\n// Encode with fingerprint header\ndata, err := schema.AppendSingleObject(nil, \u0026user)\n\n// Decode (schema known)\n_, err = schema.DecodeSingleObject(data, \u0026user)\n\n// Decode (schema unknown): extract fingerprint, look up schema\nfp, payload, err := avro.SingleObjectFingerprint(data)\nschema := registry.Lookup(fp) // your schema registry\n_, err = schema.Decode(payload, \u0026user)\n```\n\n## Fingerprinting\n\n`Canonical` returns the [Parsing Canonical Form](https://avro.apache.org/docs/current/specification/#parsing-canonical-form-for-schemas)\nof a schema — a deterministic JSON representation stripped of doc, aliases,\ndefaults, and other non-essential attributes. Use it for schema comparison and\nfingerprinting.\n\n```go\ncanonical := schema.Canonical() // []byte\n\n// CRC-64-AVRO (Rabin) — the Avro-standard fingerprint\nfp := schema.Fingerprint(avro.NewRabin())\n\n// SHA-256 — common for cross-language registries\nfp256 := schema.Fingerprint(sha256.New())\n```\n\n## Errors\n\nEncode and decode errors can be inspected with `errors.As`:\n\n- **`*SemanticError`**: type mismatch between Go and Avro (includes a dotted\n  field path for nested records, e.g. `\"address.zip\"`).\n- **`*ShortBufferError`**: input truncated mid-value.\n- **`*CompatibilityError`**: schema evolution incompatibility (from `Resolve`\n  or `CheckCompatibility`).\n\n## Performance\n\nStruct field access uses `unsafe` pointer arithmetic (similar to\n`encoding/json` v2) to avoid `reflect.Value` overhead on every encode/decode.\nAll schemas, type mappings, and codec state are cached after first use so\nrepeated operations pay no extra allocation cost.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwmb%2Favro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwmb%2Favro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwmb%2Favro/lists"}