• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 20199385855

13 Dec 2025 11:25PM UTC coverage: 94.122% (-0.05%) from 94.176%
20199385855

push

github

tyevco
refactor: Remove backwards compatibility layers from binary serialization

- Remove non-generic ToBinary/FromBinary overloads
- Remove ToBinaryStreamLegacy and FromBinaryStreamLegacy methods
- Keep only generic methods requiring both IComponentSerializer and IBinaryComponentSerializer
- Add "No Backwards Compatibility Layers" section to CLAUDE.md
- Fix tests to use explicit type arguments for null! cases

1384 of 1471 branches covered (94.09%)

Branch coverage included in aggregate %.

7519 of 7988 relevant lines covered (94.13%)

1.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.06
/src/KeenEyes.Generators/SerializationGenerator.cs
1
using System.Collections.Generic;
2
using System.Collections.Immutable;
3
using System.Linq;
4
using System.Text;
5
using Microsoft.CodeAnalysis;
6
using Microsoft.CodeAnalysis.CSharp.Syntax;
7
using Microsoft.CodeAnalysis.Text;
8

9
namespace KeenEyes.Generators;
10

11
/// <summary>
12
/// Generates AOT-compatible serialization code for components marked with [Component(Serializable = true)].
13
/// Eliminates runtime reflection by generating strongly-typed serialization methods.
14
/// </summary>
15
[Generator]
16
public sealed class SerializationGenerator : IIncrementalGenerator
17
{
18
    private const string ComponentAttribute = "KeenEyes.ComponentAttribute";
19

20
    /// <inheritdoc />
21
    public void Initialize(IncrementalGeneratorInitializationContext context)
22
    {
23
        // Find all structs with [Component(Serializable = true)]
24
        var serializableComponents = context.SyntaxProvider
1✔
25
            .ForAttributeWithMetadataName(
1✔
26
                ComponentAttribute,
1✔
27
                predicate: static (node, _) => node is StructDeclarationSyntax,
1✔
28
                transform: static (ctx, _) => GetSerializableComponentInfo(ctx))
1✔
29
            .Where(static info => info is not null);
1✔
30

31
        var collected = serializableComponents.Collect();
1✔
32

33
        // Generate the serialization registry only if there are serializable components
34
        context.RegisterSourceOutput(collected, static (ctx, components) =>
1✔
35
        {
1✔
36
            var validComponents = components
1✔
37
                .Where(c => c is not null)
1✔
38
                .Select(c => c!)
1✔
39
                .ToImmutableArray();
1✔
40

1✔
41
            // Don't generate anything if there are no serializable components
1✔
42
            // This avoids conflicts when multiple projects reference KeenEyes.Core
1✔
43
            if (validComponents.Length == 0)
1✔
44
            {
1✔
45
                return;
1✔
46
            }
1✔
47

1✔
48
            var source = GenerateSerializationRegistry(validComponents);
1✔
49
            ctx.AddSource("ComponentSerializer.g.cs", SourceText.From(source, Encoding.UTF8));
1✔
50
        });
1✔
51
    }
1✔
52

53
    private static SerializableComponentInfo? GetSerializableComponentInfo(GeneratorAttributeSyntaxContext context)
54
    {
55
        // TargetSymbol is guaranteed to be INamedTypeSymbol because we filter for StructDeclarationSyntax
56
        var typeSymbol = (INamedTypeSymbol)context.TargetSymbol;
1✔
57

58
        // Check for Serializable = true in attribute
59
        // Attributes is guaranteed non-empty because we use ForAttributeWithMetadataName
60
        var attr = context.Attributes.First();
1✔
61

62
        var serializableArg = attr.NamedArguments
1✔
63
            .FirstOrDefault(a => a.Key == "Serializable");
1✔
64

65
        if (serializableArg.Value.Value is not true)
1✔
66
        {
67
            return null;
1✔
68
        }
69

70
        // Collect fields for serialization
71
        var fields = new List<SerializableFieldInfo>();
1✔
72
        foreach (var member in typeSymbol.GetMembers())
1✔
73
        {
74
            if (member is not IFieldSymbol field)
1✔
75
            {
76
                continue;
77
            }
78

79
            if (field.IsStatic || field.IsConst)
1✔
80
            {
81
                continue;
82
            }
83

84
            fields.Add(new SerializableFieldInfo(
1✔
85
                field.Name,
1✔
86
                field.Type.ToDisplayString(),
1✔
87
                GetJsonTypeName(field.Type)));
1✔
88
        }
89

90
        return new SerializableComponentInfo(
1✔
91
            typeSymbol.Name,
1✔
92
            typeSymbol.ContainingNamespace.ToDisplayString(),
1✔
93
            typeSymbol.ToDisplayString(),
1✔
94
            fields.ToImmutableArray());
1✔
95
    }
96

97
    private static string GetJsonTypeName(ITypeSymbol type)
98
    {
99
        // Map CLR types to JSON reader method names
100
        var displayString = type.ToDisplayString();
1✔
101
        return displayString switch
1✔
102
        {
1✔
103
            "int" or "System.Int32" => "Int32",
1✔
104
            "long" or "System.Int64" => "Int64",
1✔
105
            "short" or "System.Int16" => "Int16",
×
106
            "byte" or "System.Byte" => "Byte",
×
107
            "uint" or "System.UInt32" => "UInt32",
×
108
            "ulong" or "System.UInt64" => "UInt64",
×
109
            "ushort" or "System.UInt16" => "UInt16",
×
110
            "sbyte" or "System.SByte" => "SByte",
×
111
            "float" or "System.Single" => "Single",
1✔
112
            "double" or "System.Double" => "Double",
1✔
113
            "decimal" or "System.Decimal" => "Decimal",
1✔
114
            "bool" or "System.Boolean" => "Boolean",
1✔
115
            "string" or "System.String" => "String",
1✔
116
            _ => "Object" // For complex types, use generic deserialization
1✔
117
        };
1✔
118
    }
119

120
    private static string GenerateSerializationRegistry(ImmutableArray<SerializableComponentInfo> components)
121
    {
122
        var sb = new StringBuilder();
1✔
123

124
        sb.AppendLine("// <auto-generated />");
1✔
125
        sb.AppendLine("#nullable enable");
1✔
126
        sb.AppendLine();
1✔
127
        sb.AppendLine("using System;");
1✔
128
        sb.AppendLine("using System.Collections.Generic;");
1✔
129
        sb.AppendLine("using System.IO;");
1✔
130
        sb.AppendLine("using System.Text.Json;");
1✔
131
        sb.AppendLine("using KeenEyes.Serialization;");
1✔
132
        sb.AppendLine();
1✔
133
        sb.AppendLine("namespace KeenEyes.Generated;");
1✔
134
        sb.AppendLine();
1✔
135
        sb.AppendLine("/// <summary>");
1✔
136
        sb.AppendLine("/// Generated registry for AOT-compatible component serialization.");
1✔
137
        sb.AppendLine("/// Contains serialization methods for components marked with [Component(Serializable = true)].");
1✔
138
        sb.AppendLine("/// </summary>");
1✔
139
        sb.AppendLine("public sealed class ComponentSerializer : IComponentSerializer, IBinaryComponentSerializer");
1✔
140
        sb.AppendLine("{");
1✔
141
        sb.AppendLine("    /// <summary>");
1✔
142
        sb.AppendLine("    /// Shared instance for convenience.");
1✔
143
        sb.AppendLine("    /// </summary>");
1✔
144
        sb.AppendLine("    public static readonly ComponentSerializer Instance = new();");
1✔
145
        sb.AppendLine();
1✔
146
        sb.AppendLine("    private static readonly Dictionary<string, Func<JsonElement, object>> Deserializers;");
1✔
147
        sb.AppendLine("    private static readonly Dictionary<Type, Func<object, JsonElement>> Serializers;");
1✔
148
        sb.AppendLine("    private static readonly Dictionary<string, Type> TypesByName;");
1✔
149
        sb.AppendLine("    private static readonly HashSet<Type> SerializableTypes;");
1✔
150
        sb.AppendLine("    private static readonly Dictionary<string, Func<World, bool, ComponentInfo>> Registrars;");
1✔
151
        sb.AppendLine("    private static readonly Dictionary<string, Action<World, object>> SingletonSetters;");
1✔
152
        sb.AppendLine("    private static readonly Dictionary<string, Func<BinaryReader, object>> BinaryDeserializers;");
1✔
153
        sb.AppendLine("    private static readonly Dictionary<Type, Action<object, BinaryWriter>> BinarySerializers;");
1✔
154
        sb.AppendLine();
1✔
155

156
        // Static constructor to initialize dictionaries
157
        sb.AppendLine("    static ComponentSerializer()");
1✔
158
        sb.AppendLine("    {");
1✔
159
        sb.AppendLine("        Deserializers = new Dictionary<string, Func<JsonElement, object>>");
1✔
160
        sb.AppendLine("        {");
1✔
161

162
        foreach (var component in components)
1✔
163
        {
164
            sb.AppendLine($"            [typeof({component.FullName}).AssemblyQualifiedName!] = Deserialize_{component.Name},");
1✔
165
            sb.AppendLine($"            [\"{component.FullName}\"] = Deserialize_{component.Name},");
1✔
166
        }
167

168
        sb.AppendLine("        };");
1✔
169
        sb.AppendLine();
1✔
170
        sb.AppendLine("        Serializers = new Dictionary<Type, Func<object, JsonElement>>");
1✔
171
        sb.AppendLine("        {");
1✔
172

173
        foreach (var component in components)
1✔
174
        {
175
            sb.AppendLine($"            [typeof({component.FullName})] = value => Serialize_{component.Name}(({component.FullName})value),");
1✔
176
        }
177

178
        sb.AppendLine("        };");
1✔
179
        sb.AppendLine();
1✔
180
        sb.AppendLine("        TypesByName = new Dictionary<string, Type>");
1✔
181
        sb.AppendLine("        {");
1✔
182

183
        foreach (var component in components)
1✔
184
        {
185
            sb.AppendLine($"            [typeof({component.FullName}).AssemblyQualifiedName!] = typeof({component.FullName}),");
1✔
186
            sb.AppendLine($"            [\"{component.FullName}\"] = typeof({component.FullName}),");
1✔
187
        }
188

189
        sb.AppendLine("        };");
1✔
190
        sb.AppendLine();
1✔
191
        sb.AppendLine("        SerializableTypes = new HashSet<Type>");
1✔
192
        sb.AppendLine("        {");
1✔
193

194
        foreach (var component in components)
1✔
195
        {
196
            sb.AppendLine($"            typeof({component.FullName}),");
1✔
197
        }
198

199
        sb.AppendLine("        };");
1✔
200
        sb.AppendLine();
1✔
201
        sb.AppendLine("        Registrars = new Dictionary<string, Func<World, bool, ComponentInfo>>");
1✔
202
        sb.AppendLine("        {");
1✔
203

204
        foreach (var component in components)
1✔
205
        {
206
            sb.AppendLine($"            [typeof({component.FullName}).AssemblyQualifiedName!] = (world, isTag) => world.Components.Register<{component.FullName}>(isTag),");
1✔
207
            sb.AppendLine($"            [\"{component.FullName}\"] = (world, isTag) => world.Components.Register<{component.FullName}>(isTag),");
1✔
208
        }
209

210
        sb.AppendLine("        };");
1✔
211
        sb.AppendLine();
1✔
212
        sb.AppendLine("        SingletonSetters = new Dictionary<string, Action<World, object>>");
1✔
213
        sb.AppendLine("        {");
1✔
214

215
        foreach (var component in components)
1✔
216
        {
217
            sb.AppendLine($"            [typeof({component.FullName}).AssemblyQualifiedName!] = (world, value) => world.SetSingleton(({component.FullName})value),");
1✔
218
            sb.AppendLine($"            [\"{component.FullName}\"] = (world, value) => world.SetSingleton(({component.FullName})value),");
1✔
219
        }
220

221
        sb.AppendLine("        };");
1✔
222
        sb.AppendLine();
1✔
223
        sb.AppendLine("        BinaryDeserializers = new Dictionary<string, Func<BinaryReader, object>>");
1✔
224
        sb.AppendLine("        {");
1✔
225

226
        foreach (var component in components)
1✔
227
        {
228
            sb.AppendLine($"            [typeof({component.FullName}).AssemblyQualifiedName!] = DeserializeBinary_{component.Name},");
1✔
229
            sb.AppendLine($"            [\"{component.FullName}\"] = DeserializeBinary_{component.Name},");
1✔
230
        }
231

232
        sb.AppendLine("        };");
1✔
233
        sb.AppendLine();
1✔
234
        sb.AppendLine("        BinarySerializers = new Dictionary<Type, Action<object, BinaryWriter>>");
1✔
235
        sb.AppendLine("        {");
1✔
236

237
        foreach (var component in components)
1✔
238
        {
239
            sb.AppendLine($"            [typeof({component.FullName})] = (value, writer) => SerializeBinary_{component.Name}(({component.FullName})value, writer),");
1✔
240
        }
241

242
        sb.AppendLine("        };");
1✔
243
        sb.AppendLine("    }");
1✔
244
        sb.AppendLine();
1✔
245

246
        // Public API methods implementing IComponentSerializer
247
        sb.AppendLine("    /// <inheritdoc />");
1✔
248
        sb.AppendLine("    public bool IsSerializable(Type type) => SerializableTypes.Contains(type);");
1✔
249
        sb.AppendLine();
1✔
250
        sb.AppendLine("    /// <inheritdoc />");
1✔
251
        sb.AppendLine("    public bool IsSerializable(string typeName) => Deserializers.ContainsKey(typeName);");
1✔
252
        sb.AppendLine();
1✔
253
        sb.AppendLine("    /// <inheritdoc />");
1✔
254
        sb.AppendLine("    public object? Deserialize(string typeName, JsonElement json)");
1✔
255
        sb.AppendLine("    {");
1✔
256
        sb.AppendLine("        return Deserializers.TryGetValue(typeName, out var deserializer)");
1✔
257
        sb.AppendLine("            ? deserializer(json)");
1✔
258
        sb.AppendLine("            : null;");
1✔
259
        sb.AppendLine("    }");
1✔
260
        sb.AppendLine();
1✔
261
        sb.AppendLine("    /// <inheritdoc />");
1✔
262
        sb.AppendLine("    public JsonElement? Serialize(Type type, object value)");
1✔
263
        sb.AppendLine("    {");
1✔
264
        sb.AppendLine("        if (!Serializers.TryGetValue(type, out var serializer))");
1✔
265
        sb.AppendLine("        {");
1✔
266
        sb.AppendLine("            return null;");
1✔
267
        sb.AppendLine("        }");
1✔
268
        sb.AppendLine("        return serializer(value);");
1✔
269
        sb.AppendLine("    }");
1✔
270
        sb.AppendLine();
1✔
271
        sb.AppendLine("    /// <inheritdoc />");
1✔
272
        sb.AppendLine("    Type? IComponentSerializer.GetType(string typeName)");
1✔
273
        sb.AppendLine("    {");
1✔
274
        sb.AppendLine("        return TypesByName.TryGetValue(typeName, out var type) ? type : null;");
1✔
275
        sb.AppendLine("    }");
1✔
276
        sb.AppendLine();
1✔
277
        sb.AppendLine("    /// <inheritdoc />");
1✔
278
        sb.AppendLine("    public ComponentInfo? RegisterComponent(World world, string typeName, bool isTag)");
1✔
279
        sb.AppendLine("    {");
1✔
280
        sb.AppendLine("        return Registrars.TryGetValue(typeName, out var registrar) ? registrar(world, isTag) : null;");
1✔
281
        sb.AppendLine("    }");
1✔
282
        sb.AppendLine();
1✔
283
        sb.AppendLine("    /// <inheritdoc />");
1✔
284
        sb.AppendLine("    public bool SetSingleton(World world, string typeName, object value)");
1✔
285
        sb.AppendLine("    {");
1✔
286
        sb.AppendLine("        if (SingletonSetters.TryGetValue(typeName, out var setter))");
1✔
287
        sb.AppendLine("        {");
1✔
288
        sb.AppendLine("            setter(world, value);");
1✔
289
        sb.AppendLine("            return true;");
1✔
290
        sb.AppendLine("        }");
1✔
291
        sb.AppendLine("        return false;");
1✔
292
        sb.AppendLine("    }");
1✔
293
        sb.AppendLine();
1✔
294
        sb.AppendLine("    /// <summary>");
1✔
295
        sb.AppendLine("    /// Gets all registered serializable type names.");
1✔
296
        sb.AppendLine("    /// </summary>");
1✔
297
        sb.AppendLine("    public IEnumerable<string> GetSerializableTypeNames() => Deserializers.Keys;");
1✔
298
        sb.AppendLine();
1✔
299
        sb.AppendLine("    /// <summary>");
1✔
300
        sb.AppendLine("    /// Gets all registered serializable types.");
1✔
301
        sb.AppendLine("    /// </summary>");
1✔
302
        sb.AppendLine("    public IEnumerable<Type> GetSerializableTypes() => SerializableTypes;");
1✔
303
        sb.AppendLine();
1✔
304

305
        // IBinaryComponentSerializer implementation
306
        sb.AppendLine("    /// <inheritdoc />");
1✔
307
        sb.AppendLine("    public bool WriteTo(Type type, object value, BinaryWriter writer)");
1✔
308
        sb.AppendLine("    {");
1✔
309
        sb.AppendLine("        if (!BinarySerializers.TryGetValue(type, out var serializer))");
1✔
310
        sb.AppendLine("        {");
1✔
311
        sb.AppendLine("            return false;");
1✔
312
        sb.AppendLine("        }");
1✔
313
        sb.AppendLine("        serializer(value, writer);");
1✔
314
        sb.AppendLine("        return true;");
1✔
315
        sb.AppendLine("    }");
1✔
316
        sb.AppendLine();
1✔
317
        sb.AppendLine("    /// <inheritdoc />");
1✔
318
        sb.AppendLine("    public object? ReadFrom(string typeName, BinaryReader reader)");
1✔
319
        sb.AppendLine("    {");
1✔
320
        sb.AppendLine("        return BinaryDeserializers.TryGetValue(typeName, out var deserializer)");
1✔
321
        sb.AppendLine("            ? deserializer(reader)");
1✔
322
        sb.AppendLine("            : null;");
1✔
323
        sb.AppendLine("    }");
1✔
324
        sb.AppendLine();
1✔
325

326
        // Generate individual serialization/deserialization methods
327
        foreach (var component in components)
1✔
328
        {
329
            GenerateComponentMethods(sb, component);
1✔
330
        }
331

332
        sb.AppendLine("}");
1✔
333

334
        return sb.ToString();
1✔
335
    }
336

337
    private static void GenerateComponentMethods(StringBuilder sb, SerializableComponentInfo component)
338
    {
339
        // Deserialize method
340
        sb.AppendLine($"    private static object Deserialize_{component.Name}(JsonElement json)");
1✔
341
        sb.AppendLine("    {");
1✔
342
        sb.AppendLine($"        var result = new {component.FullName}();");
1✔
343

344
        foreach (var field in component.Fields)
1✔
345
        {
346
            var camelFieldName = ToCamelCase(field.Name);
1✔
347
            sb.AppendLine($"        if (json.TryGetProperty(\"{camelFieldName}\", out var {camelFieldName}Elem))");
1✔
348
            sb.AppendLine("        {");
1✔
349

350
            if (field.JsonTypeName == "Object")
1✔
351
            {
352
                // Complex type - use generic deserialization
353
                sb.AppendLine($"            result.{field.Name} = JsonSerializer.Deserialize<{field.Type}>({camelFieldName}Elem.GetRawText())!;");
1✔
354
            }
355
            else if (field.JsonTypeName == "String")
1✔
356
            {
357
                sb.AppendLine($"            result.{field.Name} = {camelFieldName}Elem.GetString()!;");
1✔
358
            }
359
            else
360
            {
361
                sb.AppendLine($"            result.{field.Name} = {camelFieldName}Elem.Get{field.JsonTypeName}();");
1✔
362
            }
363

364
            sb.AppendLine("        }");
1✔
365
        }
366

367
        sb.AppendLine("        return result;");
1✔
368
        sb.AppendLine("    }");
1✔
369
        sb.AppendLine();
1✔
370

371
        // Serialize method
372
        sb.AppendLine($"    private static JsonElement Serialize_{component.Name}({component.FullName} value)");
1✔
373
        sb.AppendLine("    {");
1✔
374
        sb.AppendLine("        using var stream = new System.IO.MemoryStream();");
1✔
375
        sb.AppendLine("        using var writer = new Utf8JsonWriter(stream);");
1✔
376
        sb.AppendLine("        writer.WriteStartObject();");
1✔
377

378
        foreach (var field in component.Fields)
1✔
379
        {
380
            var camelFieldName = ToCamelCase(field.Name);
1✔
381

382
            if (field.JsonTypeName == "Object")
1✔
383
            {
384
                sb.AppendLine($"        writer.WritePropertyName(\"{camelFieldName}\");");
1✔
385
                sb.AppendLine($"        JsonSerializer.Serialize(writer, value.{field.Name});");
1✔
386
            }
387
            else if (field.JsonTypeName == "String")
1✔
388
            {
389
                sb.AppendLine($"        writer.WriteString(\"{camelFieldName}\", value.{field.Name});");
1✔
390
            }
391
            else if (field.JsonTypeName == "Boolean")
1✔
392
            {
393
                sb.AppendLine($"        writer.WriteBoolean(\"{camelFieldName}\", value.{field.Name});");
1✔
394
            }
395
            else
396
            {
397
                sb.AppendLine($"        writer.WriteNumber(\"{camelFieldName}\", value.{field.Name});");
1✔
398
            }
399
        }
400

401
        sb.AppendLine("        writer.WriteEndObject();");
1✔
402
        sb.AppendLine("        writer.Flush();");
1✔
403
        sb.AppendLine("        stream.Position = 0;");
1✔
404
        sb.AppendLine("        return JsonDocument.Parse(stream).RootElement.Clone();");
1✔
405
        sb.AppendLine("    }");
1✔
406
        sb.AppendLine();
1✔
407

408
        // Binary deserialize method
409
        sb.AppendLine($"    private static object DeserializeBinary_{component.Name}(BinaryReader reader)");
1✔
410
        sb.AppendLine("    {");
1✔
411
        sb.AppendLine($"        var result = new {component.FullName}();");
1✔
412

413
        foreach (var field in component.Fields)
1✔
414
        {
415
            var binaryRead = GetBinaryReadMethod(field.JsonTypeName, field.Type);
1✔
416
            sb.AppendLine($"        result.{field.Name} = {binaryRead};");
1✔
417
        }
418

419
        sb.AppendLine("        return result;");
1✔
420
        sb.AppendLine("    }");
1✔
421
        sb.AppendLine();
1✔
422

423
        // Binary serialize method
424
        sb.AppendLine($"    private static void SerializeBinary_{component.Name}({component.FullName} value, BinaryWriter writer)");
1✔
425
        sb.AppendLine("    {");
1✔
426

427
        foreach (var field in component.Fields)
1✔
428
        {
429
            var binaryWrite = GetBinaryWriteCode(field.JsonTypeName, field.Type, $"value.{field.Name}");
1✔
430
            sb.AppendLine($"        {binaryWrite}");
1✔
431
        }
432

433
        sb.AppendLine("    }");
1✔
434
        sb.AppendLine();
1✔
435
    }
1✔
436

437
    private static string GetBinaryReadMethod(string jsonTypeName, string type)
438
    {
439
        return jsonTypeName switch
1✔
440
        {
1✔
441
            "Int32" => "reader.ReadInt32()",
1✔
442
            "Int64" => "reader.ReadInt64()",
1✔
443
            "Int16" => "reader.ReadInt16()",
×
444
            "Byte" => "reader.ReadByte()",
×
445
            "UInt32" => "reader.ReadUInt32()",
×
446
            "UInt64" => "reader.ReadUInt64()",
×
447
            "UInt16" => "reader.ReadUInt16()",
×
448
            "SByte" => "reader.ReadSByte()",
×
449
            "Single" => "reader.ReadSingle()",
1✔
450
            "Double" => "reader.ReadDouble()",
1✔
451
            "Decimal" => "reader.ReadDecimal()",
1✔
452
            "Boolean" => "reader.ReadBoolean()",
1✔
453
            "String" => "reader.ReadString()",
1✔
454
            _ => $"JsonSerializer.Deserialize<{type}>(reader.ReadString())!" // Complex types as JSON
1✔
455
        };
1✔
456
    }
457

458
    private static string GetBinaryWriteCode(string jsonTypeName, string type, string valueExpr)
459
    {
460
        return jsonTypeName switch
1✔
461
        {
1✔
462
            "Int32" or "Int64" or "Int16" or "Byte" or
1✔
463
            "UInt32" or "UInt64" or "UInt16" or "SByte" or
1✔
464
            "Single" or "Double" or "Decimal" or "Boolean" or "String"
1✔
465
                => $"writer.Write({valueExpr});",
1✔
466
            _ => $"writer.Write(JsonSerializer.Serialize({valueExpr}));" // Complex types as JSON
1✔
467
        };
1✔
468
    }
469

470
    private static string ToCamelCase(string name)
471
    {
472
        if (string.IsNullOrEmpty(name))
1✔
473
        {
474
            return name;
×
475
        }
476

477
        if (name.Length == 1)
1✔
478
        {
479
            return name.ToLowerInvariant();
1✔
480
        }
481

482
        return char.ToLowerInvariant(name[0]) + name.Substring(1);
1✔
483
    }
484

485
    private sealed record SerializableComponentInfo(
1✔
486
        string Name,
1✔
487
        string Namespace,
×
488
        string FullName,
1✔
489
        ImmutableArray<SerializableFieldInfo> Fields);
1✔
490

491
    private sealed record SerializableFieldInfo(
1✔
492
        string Name,
1✔
493
        string Type,
1✔
494
        string JsonTypeName);
1✔
495
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc