This is an automated email from the ASF dual-hosted git repository.
blankensteiner pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-dotpulsar.git
The following commit(s) were added to refs/heads/master by this push:
new b4151ce Fix Schema.Json<T>() generating empty schema definition (#283)
b4151ce is described below
commit b4151ced65ca6751a911bf748b81d531a35ba285
Author: Thorben Luepkes <[email protected]>
AuthorDate: Mon Mar 2 09:52:31 2026 +0100
Fix Schema.Json<T>() generating empty schema definition (#283)
Schema.Json<T>() and Schema.Json<T>(options) passed
Array.Empty<byte>() as SchemaInfo.Data, causing the
Pulsar broker to reject producers with
IncompatibleSchemaException ("No content to map due
to end-of-input").
Add JsonSchemaDefinitionGenerator that auto-generates
an Avro-style schema definition from T via reflection.
The parameterless and options-only constructors now use
the generator. The explicit definition overload is
unchanged — it remains the escape hatch for custom
schema strings.
The generator respects JsonPropertyNameAttribute,
JsonIgnoreAttribute, and PropertyNamingPolicy. It
handles primitives, nullables, enums, collections,
dictionaries, and nested record types.
Fixes #282
---
src/DotPulsar/Schemas/JsonSchema.cs | 12 +-
.../Schemas/JsonSchemaDefinitionGenerator.cs | 193 +++++++++++++++
.../Schemas/JsonSchemaDefinitionGeneratorTests.cs | 267 +++++++++++++++++++++
tests/DotPulsar.Tests/Schemas/JsonSchemaTests.cs | 45 ++++
.../Schemas/TestSamples/JsonModels/ComplexModel.cs | 84 +++++++
5 files changed, 599 insertions(+), 2 deletions(-)
diff --git a/src/DotPulsar/Schemas/JsonSchema.cs
b/src/DotPulsar/Schemas/JsonSchema.cs
index 45f1411..4147177 100644
--- a/src/DotPulsar/Schemas/JsonSchema.cs
+++ b/src/DotPulsar/Schemas/JsonSchema.cs
@@ -31,13 +31,15 @@ public sealed class JsonSchema<T> : ISchema<T>
public JsonSchema()
{
_options = new JsonSerializerOptions();
- SchemaInfo = new SchemaInfo(typeof(T).Name, Array.Empty<byte>(),
SchemaType.Json, ImmutableDictionary<string, string>.Empty);
+ var schemaData = GenerateSchemaData(_options);
+ SchemaInfo = new SchemaInfo(typeof(T).Name, schemaData,
SchemaType.Json, ImmutableDictionary<string, string>.Empty);
}
public JsonSchema(JsonSerializerOptions options)
{
_options = options;
- SchemaInfo = new SchemaInfo(typeof(T).Name, Array.Empty<byte>(),
SchemaType.Json, ImmutableDictionary<string, string>.Empty);
+ var schemaData = GenerateSchemaData(_options);
+ SchemaInfo = new SchemaInfo(typeof(T).Name, schemaData,
SchemaType.Json, ImmutableDictionary<string, string>.Empty);
}
public JsonSchema(JsonSerializerOptions options, string
jsonSchemaDefinition)
@@ -68,4 +70,10 @@ public sealed class JsonSchema<T> : ISchema<T>
var bytes = JsonSerializer.SerializeToUtf8Bytes(message, _options);
return new ReadOnlySequence<byte>(bytes);
}
+
+ private static byte[] GenerateSchemaData(JsonSerializerOptions options)
+ {
+ var definition = JsonSchemaDefinitionGenerator.Generate<T>(options);
+ return Encoding.UTF8.GetBytes(definition);
+ }
}
diff --git a/src/DotPulsar/Schemas/JsonSchemaDefinitionGenerator.cs
b/src/DotPulsar/Schemas/JsonSchemaDefinitionGenerator.cs
new file mode 100644
index 0000000..6e398de
--- /dev/null
+++ b/src/DotPulsar/Schemas/JsonSchemaDefinitionGenerator.cs
@@ -0,0 +1,193 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace DotPulsar.Schemas;
+
+using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+/// <summary>
+/// Generates Avro-style JSON schema definitions from .NET types.
+/// Used by <see cref="JsonSchema{T}"/> to produce broker-compatible schema
data.
+/// </summary>
+public static class JsonSchemaDefinitionGenerator
+{
+ /// <summary>
+ /// Generate an Avro-style schema definition for the given type.
+ /// </summary>
+ public static string Generate<T>(JsonSerializerOptions? options = null)
+ => Generate(typeof(T), options);
+
+ /// <summary>
+ /// Generate an Avro-style schema definition for the given type.
+ /// </summary>
+ public static string Generate(Type type, JsonSerializerOptions? options =
null)
+ {
+ var namingPolicy = options?.PropertyNamingPolicy;
+ var sb = new StringBuilder();
+ GenerateRecord(type, namingPolicy, sb);
+ return sb.ToString();
+ }
+
+ private static void GenerateRecord(Type type, JsonNamingPolicy?
namingPolicy, StringBuilder sb)
+ {
+ sb.Append("{\"type\":\"record\",\"name\":\"");
+ sb.Append(type.Name);
+ sb.Append("\",\"namespace\":\"");
+ sb.Append(type.Namespace ?? string.Empty);
+ sb.Append("\",\"fields\":[");
+
+ var properties = type.GetProperties(BindingFlags.Public |
BindingFlags.Instance);
+ var first = true;
+
+ foreach (var property in properties)
+ {
+ if (property.GetCustomAttribute<JsonIgnoreAttribute>() is not null)
+ continue;
+
+ if (!first)
+ sb.Append(',');
+
+ first = false;
+
+ var fieldName = ResolveFieldName(property, namingPolicy);
+ var avroType = MapToAvroType(property.PropertyType, namingPolicy,
sb, fieldNameOnly: true);
+
+ sb.Append("{\"name\":\"");
+ sb.Append(fieldName);
+ sb.Append("\",\"type\":");
+ sb.Append(avroType);
+ sb.Append('}');
+ }
+
+ sb.Append("]}");
+ }
+
+ private static string ResolveFieldName(PropertyInfo property,
JsonNamingPolicy? namingPolicy)
+ {
+ var jsonPropertyName =
property.GetCustomAttribute<JsonPropertyNameAttribute>();
+ if (jsonPropertyName is not null)
+ return jsonPropertyName.Name;
+
+ return namingPolicy?.ConvertName(property.Name) ?? property.Name;
+ }
+
+ private static string MapToAvroType(Type type, JsonNamingPolicy?
namingPolicy, StringBuilder sb, bool fieldNameOnly)
+ {
+ var underlyingType = Nullable.GetUnderlyingType(type);
+ if (underlyingType is not null)
+ {
+ var innerAvro = MapToAvroType(underlyingType, namingPolicy, sb,
fieldNameOnly);
+ return "[\"null\"," + innerAvro + "]";
+ }
+
+ if (type == typeof(string))
+ return "\"string\"";
+
+ if (type == typeof(int))
+ return "\"int\"";
+
+ if (type == typeof(long))
+ return "\"long\"";
+
+ if (type == typeof(float))
+ return "\"float\"";
+
+ if (type == typeof(double))
+ return "\"double\"";
+
+ if (type == typeof(bool))
+ return "\"boolean\"";
+
+ if (type == typeof(byte[]))
+ return "\"bytes\"";
+
+ if (type == typeof(decimal))
+ return "\"double\"";
+
+ if (type == typeof(short))
+ return "\"int\"";
+
+ if (type == typeof(byte))
+ return "\"int\"";
+
+ if (type == typeof(DateTime) || type == typeof(DateTimeOffset))
+ return "\"string\"";
+
+ if (type == typeof(Guid))
+ return "\"string\"";
+
+ if (type.IsEnum)
+ {
+ var names = Enum.GetNames(type);
+ var enumSb = new StringBuilder();
+ enumSb.Append("{\"type\":\"enum\",\"name\":\"");
+ enumSb.Append(type.Name);
+ enumSb.Append("\",\"symbols\":[");
+ for (var i = 0; i < names.Length; i++)
+ {
+ if (i > 0)
+ enumSb.Append(',');
+ enumSb.Append('"');
+ enumSb.Append(names[i]);
+ enumSb.Append('"');
+ }
+ enumSb.Append("]}");
+ return enumSb.ToString();
+ }
+
+ if (type.IsArray)
+ {
+ var elementType = type.GetElementType()!;
+ var itemType = MapToAvroType(elementType, namingPolicy, sb,
fieldNameOnly);
+ return "{\"type\":\"array\",\"items\":" + itemType + "}";
+ }
+
+ if (type.IsGenericType)
+ {
+ var genericDef = type.GetGenericTypeDefinition();
+
+ if (genericDef == typeof(List<>) ||
+ genericDef == typeof(IList<>) ||
+ genericDef == typeof(IEnumerable<>) ||
+ genericDef == typeof(ICollection<>) ||
+ genericDef == typeof(IReadOnlyList<>) ||
+ genericDef == typeof(IReadOnlyCollection<>))
+ {
+ var itemType = MapToAvroType(type.GetGenericArguments()[0],
namingPolicy, sb, fieldNameOnly);
+ return "{\"type\":\"array\",\"items\":" + itemType + "}";
+ }
+
+ if (genericDef == typeof(Dictionary<,>) ||
+ genericDef == typeof(IDictionary<,>) ||
+ genericDef == typeof(IReadOnlyDictionary<,>))
+ {
+ var valueType = MapToAvroType(type.GetGenericArguments()[1],
namingPolicy, sb, fieldNameOnly);
+ return "{\"type\":\"map\",\"values\":" + valueType + "}";
+ }
+ }
+
+ // Nested record type — generate inline
+ if (type.IsClass || type.IsValueType && !type.IsPrimitive)
+ {
+ var nestedSb = new StringBuilder();
+ GenerateRecord(type, namingPolicy, nestedSb);
+ return nestedSb.ToString();
+ }
+
+ return "\"string\"";
+ }
+}
diff --git
a/tests/DotPulsar.Tests/Schemas/JsonSchemaDefinitionGeneratorTests.cs
b/tests/DotPulsar.Tests/Schemas/JsonSchemaDefinitionGeneratorTests.cs
new file mode 100644
index 0000000..3e94a08
--- /dev/null
+++ b/tests/DotPulsar.Tests/Schemas/JsonSchemaDefinitionGeneratorTests.cs
@@ -0,0 +1,267 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace DotPulsar.Tests.Schemas;
+
+using DotPulsar.Schemas;
+using DotPulsar.Tests.Schemas.TestSamples.JsonModels;
+using System.Text.Json;
+
+[Trait("Category", "Unit")]
+public sealed class JsonSchemaDefinitionGeneratorTests
+{
+ [Fact]
+ public void Generate_GivenSimpleType_ShouldProduceValidAvroSchema()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<PersonModel>();
+
+ //Assert
+ schema.ShouldContain("\"type\":\"record\"");
+ schema.ShouldContain("\"name\":\"PersonModel\"");
+
schema.ShouldContain("\"namespace\":\"DotPulsar.Tests.Schemas.TestSamples.JsonModels\"");
+ schema.ShouldContain("\"fields\":");
+ }
+
+ [Fact]
+ public void Generate_GivenSimpleType_ShouldIncludeAllPublicProperties()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<PersonModel>();
+
+ //Assert
+ schema.ShouldContain("\"name\":\"Name\"");
+ schema.ShouldContain("\"name\":\"Age\"");
+ }
+
+ [Fact]
+ public void Generate_GivenStringProperty_ShouldMapToAvroString()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<PersonModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var nameField = FindField(fields, "Name");
+
+ //Assert
+ nameField.GetProperty("type").GetString().ShouldBe("string");
+ }
+
+ [Fact]
+ public void Generate_GivenIntProperty_ShouldMapToAvroInt()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<PersonModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var ageField = FindField(fields, "Age");
+
+ //Assert
+ ageField.GetProperty("type").GetString().ShouldBe("int");
+ }
+
+ [Fact]
+ public void Generate_GivenAllPrimitiveTypes_ShouldMapCorrectly()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<AllTypesModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+
+ //Assert
+ FindField(fields,
"StringField").GetProperty("type").GetString().ShouldBe("string");
+ FindField(fields,
"IntField").GetProperty("type").GetString().ShouldBe("int");
+ FindField(fields,
"LongField").GetProperty("type").GetString().ShouldBe("long");
+ FindField(fields,
"FloatField").GetProperty("type").GetString().ShouldBe("float");
+ FindField(fields,
"DoubleField").GetProperty("type").GetString().ShouldBe("double");
+ FindField(fields,
"BoolField").GetProperty("type").GetString().ShouldBe("boolean");
+ FindField(fields,
"DecimalField").GetProperty("type").GetString().ShouldBe("double");
+ }
+
+ [Fact]
+ public void Generate_GivenNullableProperty_ShouldMapToAvroUnion()
+ {
+ //Act
+ var schema =
JsonSchemaDefinitionGenerator.Generate<NullableFieldModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var optionalAge = FindField(fields, "OptionalAge");
+
+ //Assert
+ var type = optionalAge.GetProperty("type");
+ type.ValueKind.ShouldBe(JsonValueKind.Array);
+ type[0].GetString().ShouldBe("null");
+ type[1].GetString().ShouldBe("int");
+ }
+
+ [Fact]
+ public void
Generate_GivenCamelCaseNamingPolicy_ShouldUseCamelCaseFieldNames()
+ {
+ //Arrange
+ var options = new JsonSerializerOptions { PropertyNamingPolicy =
JsonNamingPolicy.CamelCase };
+
+ //Act
+ var schema =
JsonSchemaDefinitionGenerator.Generate<PersonModel>(options);
+
+ //Assert — verify field names are camelCase, parse to check precisely
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var fieldNames = new List<string>();
+ foreach (var field in fields.EnumerateArray())
+ fieldNames.Add(field.GetProperty("name").GetString()!);
+
+ fieldNames.ShouldContain("name");
+ fieldNames.ShouldContain("age");
+ fieldNames.ShouldNotContain("Name");
+ fieldNames.ShouldNotContain("Age");
+ }
+
+ [Fact]
+ public void
Generate_GivenJsonPropertyNameAttribute_ShouldUseAttributeValue()
+ {
+ //Act
+ var schema =
JsonSchemaDefinitionGenerator.Generate<JsonPropertyNameModel>();
+
+ //Assert
+ schema.ShouldContain("\"name\":\"display_name\"");
+ schema.ShouldContain("\"name\":\"created_at\"");
+ schema.ShouldNotContain("\"name\":\"DisplayName\"");
+ }
+
+ [Fact]
+ public void
Generate_GivenJsonPropertyNameAttribute_ShouldTakePrecedenceOverNamingPolicy()
+ {
+ //Arrange
+ var options = new JsonSerializerOptions { PropertyNamingPolicy =
JsonNamingPolicy.CamelCase };
+
+ //Act
+ var schema =
JsonSchemaDefinitionGenerator.Generate<JsonPropertyNameModel>(options);
+
+ //Assert — attribute wins over naming policy
+ schema.ShouldContain("\"name\":\"display_name\"");
+ schema.ShouldContain("\"name\":\"created_at\"");
+ }
+
+ [Fact]
+ public void Generate_GivenJsonIgnoreAttribute_ShouldExcludeProperty()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<JsonIgnoreModel>();
+
+ //Assert
+ schema.ShouldContain("\"name\":\"Name\"");
+ schema.ShouldNotContain("Secret");
+ }
+
+ [Fact]
+ public void Generate_GivenEnumProperty_ShouldMapToAvroEnum()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<EnumModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var priorityField = FindField(fields, "Priority");
+
+ //Assert
+ var type = priorityField.GetProperty("type");
+ type.GetProperty("type").GetString().ShouldBe("enum");
+ type.GetProperty("name").GetString().ShouldBe("Priority");
+ var symbols = type.GetProperty("symbols");
+ symbols[0].GetString().ShouldBe("Low");
+ symbols[1].GetString().ShouldBe("Medium");
+ symbols[2].GetString().ShouldBe("High");
+ }
+
+ [Fact]
+ public void Generate_GivenNestedType_ShouldGenerateInlineRecord()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<NestedModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var authorField = FindField(fields, "Author");
+
+ //Assert
+ var type = authorField.GetProperty("type");
+ type.GetProperty("type").GetString().ShouldBe("record");
+ type.GetProperty("name").GetString().ShouldBe("PersonModel");
+ }
+
+ [Fact]
+ public void Generate_GivenListProperty_ShouldMapToAvroArray()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<CollectionModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var tagsField = FindField(fields, "Tags");
+
+ //Assert
+ var type = tagsField.GetProperty("type");
+ type.GetProperty("type").GetString().ShouldBe("array");
+ type.GetProperty("items").GetString().ShouldBe("string");
+ }
+
+ [Fact]
+ public void Generate_GivenArrayProperty_ShouldMapToAvroArray()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<CollectionModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var scoresField = FindField(fields, "Scores");
+
+ //Assert
+ var type = scoresField.GetProperty("type");
+ type.GetProperty("type").GetString().ShouldBe("array");
+ type.GetProperty("items").GetString().ShouldBe("int");
+ }
+
+ [Fact]
+ public void Generate_GivenDictionaryProperty_ShouldMapToAvroMap()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<DictionaryModel>();
+ var parsed = JsonDocument.Parse(schema);
+ var fields = parsed.RootElement.GetProperty("fields");
+ var metadataField = FindField(fields, "Metadata");
+
+ //Assert
+ var type = metadataField.GetProperty("type");
+ type.GetProperty("type").GetString().ShouldBe("map");
+ type.GetProperty("values").GetString().ShouldBe("int");
+ }
+
+ [Fact]
+ public void Generate_ShouldProduceValidJson()
+ {
+ //Act
+ var schema = JsonSchemaDefinitionGenerator.Generate<PersonModel>();
+
+ //Assert — should not throw
+ var parsed = JsonDocument.Parse(schema);
+ parsed.RootElement.ValueKind.ShouldBe(JsonValueKind.Object);
+ }
+
+ private static JsonElement FindField(JsonElement fields, string name)
+ {
+ foreach (var field in fields.EnumerateArray())
+ {
+ if (field.GetProperty("name").GetString() == name)
+ return field;
+ }
+
+ throw new InvalidOperationException($"Field '{name}' not found in
schema fields");
+ }
+}
diff --git a/tests/DotPulsar.Tests/Schemas/JsonSchemaTests.cs
b/tests/DotPulsar.Tests/Schemas/JsonSchemaTests.cs
index 5e30ee7..fa8b306 100644
--- a/tests/DotPulsar.Tests/Schemas/JsonSchemaTests.cs
+++ b/tests/DotPulsar.Tests/Schemas/JsonSchemaTests.cs
@@ -182,4 +182,49 @@ public sealed class JsonSchemaTests
//Assert
exception.ShouldBeOfType<DotPulsar.Exceptions.SchemaSerializationException>();
}
+
+ [Fact]
+ public void Constructor_GivenNoArguments_ShouldGenerateNonEmptySchemaData()
+ {
+ //Act
+ var schema = new JsonSchema<PersonModel>();
+
+ //Assert
+ schema.SchemaInfo.Data.Length.ShouldBeGreaterThan(0);
+ var schemaData =
System.Text.Encoding.UTF8.GetString(schema.SchemaInfo.Data);
+ schemaData.ShouldContain("\"type\":\"record\"");
+ schemaData.ShouldContain("\"name\":\"PersonModel\"");
+ }
+
+ [Fact]
+ public void
Constructor_GivenCustomOptions_ShouldGenerateNonEmptySchemaData()
+ {
+ //Arrange
+ var options = new JsonSerializerOptions { PropertyNamingPolicy =
JsonNamingPolicy.CamelCase };
+
+ //Act
+ var schema = new JsonSchema<PersonModel>(options);
+
+ //Assert
+ schema.SchemaInfo.Data.Length.ShouldBeGreaterThan(0);
+ var schemaData =
System.Text.Encoding.UTF8.GetString(schema.SchemaInfo.Data);
+ schemaData.ShouldContain("\"type\":\"record\"");
+ schemaData.ShouldContain("\"name\":\"name\"");
+ schemaData.ShouldContain("\"name\":\"age\"");
+ }
+
+ [Fact]
+ public void Constructor_GivenExplicitDefinition_ShouldNotAutoGenerate()
+ {
+ //Arrange
+ var customDefinition = "{\"custom\":\"schema\"}";
+ var options = new JsonSerializerOptions();
+
+ //Act
+ var schema = new JsonSchema<PersonModel>(options, customDefinition);
+
+ //Assert — explicit definition should be used as-is
+ var schemaData =
System.Text.Encoding.UTF8.GetString(schema.SchemaInfo.Data);
+ schemaData.ShouldBe(customDefinition);
+ }
}
diff --git
a/tests/DotPulsar.Tests/Schemas/TestSamples/JsonModels/ComplexModel.cs
b/tests/DotPulsar.Tests/Schemas/TestSamples/JsonModels/ComplexModel.cs
new file mode 100644
index 0000000..c34d645
--- /dev/null
+++ b/tests/DotPulsar.Tests/Schemas/TestSamples/JsonModels/ComplexModel.cs
@@ -0,0 +1,84 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace DotPulsar.Tests.Schemas.TestSamples.JsonModels;
+
+using System.Text.Json.Serialization;
+
+public sealed class NullableFieldModel
+{
+ public string Name { get; set; } = string.Empty;
+ public int? OptionalAge { get; set; }
+ public double? OptionalScore { get; set; }
+}
+
+public sealed class NestedModel
+{
+ public string Title { get; set; } = string.Empty;
+ public PersonModel Author { get; set; } = new();
+}
+
+public sealed class CollectionModel
+{
+ public string Name { get; set; } = string.Empty;
+ public List<string> Tags { get; set; } = [];
+ public int[] Scores { get; set; } = [];
+}
+
+public sealed class JsonPropertyNameModel
+{
+ [JsonPropertyName("display_name")]
+ public string DisplayName { get; set; } = string.Empty;
+
+ [JsonPropertyName("created_at")]
+ public string CreatedAt { get; set; } = string.Empty;
+}
+
+public sealed class JsonIgnoreModel
+{
+ public string Name { get; set; } = string.Empty;
+
+ [JsonIgnore]
+ public string Secret { get; set; } = string.Empty;
+}
+
+public enum Priority
+{
+ Low,
+ Medium,
+ High
+}
+
+public sealed class EnumModel
+{
+ public string Name { get; set; } = string.Empty;
+ public Priority Priority { get; set; }
+}
+
+public sealed class AllTypesModel
+{
+ public string StringField { get; set; } = string.Empty;
+ public int IntField { get; set; }
+ public long LongField { get; set; }
+ public float FloatField { get; set; }
+ public double DoubleField { get; set; }
+ public bool BoolField { get; set; }
+ public decimal DecimalField { get; set; }
+}
+
+public sealed class DictionaryModel
+{
+ public string Name { get; set; } = string.Empty;
+ public Dictionary<string, int> Metadata { get; set; } = new();
+}