/// <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)); }