/// <summary>
        /// Builds a deserializer for the Confluent wire format.
        /// </summary>
        /// <typeparam name="T">
        /// The type to be deserialized.
        /// </typeparam>
        /// <param name="id">
        /// A schema ID that all payloads must be serialized with. If a received schema ID does not
        /// match this ID, <see cref="InvalidEncodingException" /> will be thrown.
        /// </param>
        /// <param name="json">
        /// The schema to build the Avro deserializer from.
        /// </param>
        /// <param name="tombstoneBehavior">
        /// The behavior of the deserializer on tombstone records.
        /// </param>
        /// <returns>
        /// A <see cref="IDeserializer{T}" /> based on <paramref name="json" />.
        /// </returns>
        protected virtual IDeserializer <T> Build <T>(
            int id,
            string json,
            TombstoneBehavior tombstoneBehavior)
        {
            var schema = SchemaReader.Read(json);

            if (tombstoneBehavior != TombstoneBehavior.None)
            {
                if (default(T) != null)
                {
                    throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values.");
                }

                var hasNull = schema is NullSchema ||
                              (schema is UnionSchema union && union.Schemas.Any(s => s is NullSchema));

                if (tombstoneBehavior == TombstoneBehavior.Strict && hasNull)
                {
                    throw new UnsupportedSchemaException(schema, "Tombstone deserialization is not supported for schemas that can represent null values.");
                }
            }

            var inner = DeserializerBuilder.BuildDelegateExpression <T>(schema);
            var span  = Expression.Parameter(typeof(ReadOnlySpan <byte>));

            var readerConstructor = inner.Parameters[0].Type
                                    .GetConstructor(new[] { span.Type });

            if (schema is BytesSchema)
            {
                inner = new WireFormatBytesDeserializerRewriter(span)
                        .VisitAndConvert(inner, GetType().Name);
            }

            return(new DelegateDeserializer <T>(
                       Expression
                       .Lambda <DelegateDeserializer <T> .Implementation>(
                           Expression.Invoke(
                               inner,
                               Expression.New(readerConstructor, span)),
                           new[] { span })
                       .Compile(),
                       id,
                       tombstoneBehavior));
        }