schema-registry icon indicating copy to clipboard operation
schema-registry copied to clipboard

Schema with oneOf always fails

Open AntonyLittle opened this issue 1 year ago • 2 comments

I have a schema to support a Kafka message that makes use of polymorphism. However, the schema always fails to validate, giving the error below, despite validating fine at https://www.jsonschemavalidator.net/ . Am I doing something wrong?

Exception thrown: 'Confluent.Kafka.ProduceException`2' in System.Private.CoreLib.dll: 'Local: Value serialization error'
 Inner exceptions found, see $exception in variables window for more details.
 Innermost exception 	 System.IO.InvalidDataException : Schema validation failed for properties: [#/Payload.Message]
   at Confluent.SchemaRegistry.Serdes.JsonSerializer`1.<SerializeAsync>d__18.MoveNext()
   at Confluent.Kafka.SyncOverAsync.SyncOverAsyncSerializer`1.Serialize(T data, SerializationContext context)
   at Confluent.Kafka.Producer`2.<ProduceAsync>d__57.MoveNext()

My serializer config is:

        builder.SetValueSerializer(
            new JsonSerializer<AllTheThings>(
                new CachedSchemaRegistryClient(
                    new SchemaRegistryConfig
                    {
                        Url = "localhost:8081"
                    }
                ), 
                new JsonSerializerConfig { 
                    AutoRegisterSchemas = false, 
                    UseLatestVersion = true,
                    SubjectNameStrategy = SubjectNameStrategy.TopicRecord
                }
            ).AsSyncOverAsync()
        );

The schema and classes are as follows:

Schema

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "OutputMessage",
    "type": "object",
    "additionalProperties": false,
    "required": [
        "Payload"
    ],
    "properties": {
        "Payload": {
            "oneOf": [
                {
                    "$ref": "#/definitions/AllTheThings"
                },
                {
                    "$ref": "#/definitions/AllTheThingsKafkaMessagePayloadA"
                },
                {
                    "$ref": "#/definitions/AllTheThingsOutputPayload"
                }
            ]
        }
    },
    "definitions": {
        "AllTheThings": {
            "type": "object",
            "additionalProperties": false,
            "required": [
                "MessageType"
            ],
            "properties": {
                "MessageType": {
                    "type": "string",
                    "minLength": 1
                }
            }
        },
        "AllTheThingsKafkaMessagePayloadA": {
            "title": "AllTheThingsKafkaMessagePayloadA",
            "type": "object",
            "additionalProperties": false,
            "required": [
                "MessageType",
                "Message"
            ],
            "properties": {
                "MessageType": {
                    "type": "string",
                    "minLength": 1
                },
                "Message": {
                    "type": "string",
                    "minLength": 1
                }
            }
        },
        "AllTheThingsOutputPayload": {
            "title": "AllTheThingsOutputPayload",
            "type": "object",
            "additionalProperties": false,
            "required": [
                "MessageType",
                "Wombats"
            ],
            "properties": {
                "MessageType": {
                    "type": "string",
                    "minLength": 1
                },
                "Hairyness": {
                    "type": [
                        "null",
                        "number"
                    ],
                    "format": "double",
                    "maximum": 100.0
                },
                "Wombats": {
                    "type": "integer",
                    "format": "int32",
                    "maximum": 2147483647.0,
                    "minimum": 5.0
                },
                "SomeText": {
                    "type": [
                        "null",
                        "string"
                    ]
                }
            }
        }
    }
}

Classes


[JsonPolymorphic(TypeDiscriminatorPropertyName = "MessageType")]
[JsonDerivedType(typeof(AllTheThingsKafkaMessagePayloadA), typeDiscriminator: "KafkaMessagePayloadA")]
[JsonDerivedType(typeof(AllTheThingsOutputPayload), typeDiscriminator: "OutputPayload")]
public class AllTheThings : IEquatable<AllTheThings>
{
    [Required]
    public string MessageType { get; set; }
    
    public bool Equals(AllTheThings? other)
    {
        if(other == null)
        {
            return false;
        }

        return
            other != null &&
            MessageType.Equals(other.MessageType)
            ; 
    }
}

    
public class AllTheThingsKafkaMessagePayloadA : AllTheThings, IEquatable<AllTheThingsKafkaMessagePayloadA>
{
    /**
     * Any old text
     */
    [Required]
    public string Message { get; set; }
    
    
    public bool Equals(AllTheThingsKafkaMessagePayloadA? other)
    {
        if(other == null)
        {
            return false;
        }

        return
            other != null &&
            base.Equals(other) &&
            Message.Equals(other.Message)
            ; 
    }
}

    
public class AllTheThingsOutputPayload : AllTheThings, IEquatable<AllTheThingsOutputPayload>
{
    /**
     * Amount of hair as a percentage
     */
    [Range(Double.MinValue, 100)]
    public Double? Hairyness { get; set; }
    
    /**
     * Number of wombats
     */
    [Required]
    [Range(5, Int32.MaxValue)]
    public Int32 Wombats { get; set; }
    
    public string? SomeText { get; set; }
    
    
    public bool Equals(AllTheThingsOutputPayload? other)
    {
        if(other == null)
        {
            return false;
        }

        return
            other != null &&
            base.Equals(other) &&
            ((Hairyness == null && other.Hairyness == null) || (Hairyness != null && Hairyness.Equals(other.Hairyness))) &&
            Wombats.Equals(other.Wombats)&&
            ((SomeText == null && other.SomeText == null) || (SomeText != null && SomeText.Equals(other.SomeText)))
            ; 
    }
}

AntonyLittle avatar Jul 31 '23 09:07 AntonyLittle

Did you ever find a resolution or a work around?

bobcat1506 avatar Oct 26 '23 13:10 bobcat1506

Did you ever find a resolution or a work around?

I did. I'm not permitted to share code, but I've described what I did here: https://github.com/confluentinc/confluent-kafka-dotnet/issues/2090

AntonyLittle avatar Oct 26 '23 14:10 AntonyLittle