You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@avro.apache.org by "Kriskras99 (via GitHub)" <gi...@apache.org> on 2026/01/09 21:28:41 UTC

Re: [I] Enums don't serialize correctly [avro-rs]

Kriskras99 commented on issue #365:
URL: https://github.com/apache/avro-rs/issues/365#issuecomment-3730627801

   Over the past month I've done a lot of thinking about how to improve the enum support in Avro and I think I finally have something that can work. It's something that's workable in both the derive code and the general code, and remains compatible with current schemas.
   
   # Current state of Rust enum support in Avro
   
   ## Plain enums
   Plain enums are serialized as Avro enums (only enum support by `#[derive(AvroSchema)]`):
   ```rust
   pub enum Foo {
       A,
       B,
       C,
   }
   ```
   ```json
   {
     "name": "Foo",
     "type": "enum",
     "symbols": ["A", "B", "C"]
   }
   ```
   
   ## Data enums
   Data enums are serialized as a record with a discriminator field (Avro enum) and a value field (Avro union):
   ```rust
   pub struct Bar {
       integer: i32,
   }
   pub enum Foo {
       A {
           field: String,
       },
       B(Bar)
   }
   ```
   ```json
   {
     "name": "Foo",
     "type": "record",
     "fields": [
       {
         "name": "type",
         "type": {
           "type": "enum",
           "symbols": ["A", "B"]
         }
       },
       {
         "name": "value",
         "type": [
           {
             "type": "record",
             "fields": [
               {
                 "name": "field",
                 "type": "string"
               }
             ]
           },
           {
             "name": "Bar",
             "type": "record",
             "fields": [
               {
                 "name": "integer",
                 "type": "int"
               }
             ]
           }
         ]
       }
     ]
   }
   ```
   The advantage of this approach is that it works for enums where multiple variants of the same type. It does not currently
   work with mixed enums as a unit variant will always be encoded as an Avro enum.
   
   ## Options
   Options have special support in the encoding logic to always produce a bare union.
   ```rust
   type Foo = Option<String>;
   ```
   ```json
   [
     "null",
     "string"
   ]
   ```
   
   # Alternative representations
   These alternative representations support mixed enums.
   
   ## Bare union
   If all types are unique, then a regular union can be used:
   ```rust
   pub enum Foo {
       A {
           field: String,
       },
       B(Bar),
       C,
   }
   ```
   ```json
   [
     {
       "type": "record",
       "name": "A",
       "fields": [
         {
           "name": "field",
           "type": "string"
         }
       ]
     },
     {
       "name": "Bar",
       "type": "record",
       "fields": [
         {
           "name": "integer",
           "type": "int"
         }
       ]
     },
     "null"
   ]
   ```
   Using the variant name as the namespace can prevent collisions for named schema types, this however doesn't work for
   unnamed types.
   
   ## Union with a record for every variant
   ```rust
   pub enum Foo {
       A {
           field: String,
       },
       B(Bar),
       C,
   }
   ```
   ```json
   [
     {
       "type": "record",
       "name": "A",
       "fields": [
         {
           "name": "field",
           "type": "string"
         }
       ]
     },
     {
       "type": "record",
       "name": "B",
       "fields": [
         {
           "name": "inner",
           "type": {
             "name": "Bar",
             "type": "record",
             "fields": [
               {
                 "name": "integer",
                 "type": "int"
               }
             ]
           }
         }
       ]
     },
     {
       "type": "record",
       "name": "C",
       "fields": [
         {
           "name": "inner",
           "type": "null"
         }
       ]
     }
   ]
   ```
   This representation will always work and is always as efficient when binary encoded compared to the bare union. However, 
   the schema definition does become larger inflating the JSON but also the in-memory representation.
   
   # Proposal
   
   ## Deriving
   It would be good to broaden the support of enums we can (de)serialize, I suggest the following schema derive strategy:
   1. If it is a plain enum, emit an Avro enum
   2. If it is a mixed/data enum, try to emit an Avro union
   3. If that fails, emit an Avro record with a `type` and `value` field
   
   ## Encoding/decoding
   When encoding or decoding we would just look at the schema to see what is expected. This does mean changing the signature
   of `to_value(value: S) -> Result<Value, Error>` to `to_value(value: S, schema: &Schema) -> Result<Value, Error>`. We could
   also put a bound on `S` that it has to implement `AvroSchema`, but that's not good performance wise as it cannot be cached.
   
   P.S. While thinking about this problem, it occurred to me that it's possible that the `AvroSchema` derive can produce
   invalid schemas (fields with the same name because of a `rename`, `Option<T>` where `T`'s schema is `null`). Users who
   are creating a schema by hand (either completely or generating schemas in code) can of course also have this issue. I
   think it would be a good idea to add a `Schema::validate(&self) -> Result<(), Error>` function so we can validate the
   generated schema in the derive, and users can check their own schemas.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org