Recursive AI

Serialize transcript JSON with NativeAOT in C#

OneSpeech is a free, open-source speech recognition library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Save transcript JSON without reflection; no model download is needed.

Terminal
dotnet add package RecursiveAI.OneSpeech

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneSpeech;
using System.Text.Json;
using System.Text.Json.Serialization;
var transcript = new Transcript
    { Text = "Hello." };
var typeInfo =
    AppJsonContext.Default.Transcript;
File.WriteAllText("transcript.json",
    JsonSerializer.Serialize(
        transcript, typeInfo));
Console.WriteLine("Saved transcript.json.");
[JsonSerializable(typeof(Transcript))]
internal partial class AppJsonContext
    : JsonSerializerContext;

Unzip the example, then dotnet run.

Prints:

Text
Saved transcript.json.

AppJsonContext is generated from the partial class in the snippet. The zip’s project disables reflection-based JSON defaults:

XML
<PropertyGroup>
  <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

The full program

C#
using OneSpeech;
using System.Text.Json;
using System.Text.Json.Serialization;
 
var transcript = new Transcript { Text = "Hello." };
var typeInfo = AppJsonContext.Default.Transcript;
string json = JsonSerializer.Serialize(transcript, typeInfo);
File.WriteAllText("transcript.json", json);
var restored = JsonSerializer.Deserialize(json, typeInfo)
    ?? throw new InvalidOperationException("Missing transcript.");
Console.WriteLine("Saved transcript.json.");
Console.WriteLine($"Equal: {transcript == restored}");
Console.WriteLine($"Reflection enabled: {JsonSerializer.IsReflectionEnabledByDefault}");
 
[JsonSerializable(typeof(Transcript))]
internal partial class AppJsonContext : JsonSerializerContext;

The full program reads the JSON back through generated metadata and prints the reflection setting. Invalid documents raise JsonException through System.Text.Json.

Text
Saved transcript.json.
Equal: True
Reflection enabled: False

The same program was published and run as a Linux x64 native executable with GCC:

Terminal
dotnet publish -c Release -r linux-x64 -p:PublishAot=true -p:CppCompilerAndLinker=gcc -o native
./native/Example

It printed the same three lines shown above. Use your platform’s NativeAOT toolchain when publishing elsewhere.

Variations

Register the generated context in serializer options instead:

C#
using OneSpeech;
using System.Text.Json;
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions
{
    TypeInfoResolver = AppJsonContext.Default
};
var transcript = new Transcript
    { Text = "Hello." };
string json = JsonSerializer.Serialize(transcript, options);
var restored = JsonSerializer.Deserialize<Transcript>(json, options);
Console.WriteLine(transcript == restored);
[JsonSerializable(typeof(Transcript))]
internal partial class AppJsonContext
    : JsonSerializerContext;
Text
True

The short answer

Register Transcript with [JsonSerializable] on a partial JsonSerializerContext, then use the serializer overloads that accept the generated JsonTypeInfo<Transcript>. Set JsonSerializerIsReflectionEnabledByDefault to false in the project to exercise this path even when running normally with dotnet run.

This page addresses JSON export and import, not model inference under NativeAOT. The sample constructs a transcript in memory and downloads no models. Its native executable writes the same transcript JSON and restores an equal value with reflection disabled.

How do I serialize JSON with NativeAOT in C#?

The context class in the snippet is an input to the System.Text.Json source generator. AppJsonContext.Default.Transcript is generated during compilation; it is not a property you implement manually. Keep the class partial and include the JsonSerializable attribute naming Transcript.

Passing the generated type information explicitly makes the required metadata visible at the call site. Do not switch back to a bare JsonSerializer.Serialize(transcript) call just because it works in a development build with reflection enabled. Microsoft’s source-generation guide describes the context and type-info overloads and explains how to disable reflection defaults consistently across runtimes.

OneSpeech’s transcript converter remains in control of the wire format. The result is the schema-versioned envelope used by Transcript.ToJson, not a second JSON shape generated from public property names. Naming policies cannot turn the transcript into a different schema. Seconds, enum names and explicit nulls follow the transcript contract.

The project in the zip targets .NET 10 and pins the released OneSpeech package. The reflection setting above is part of that project. Native publishing additionally needs the platform’s native toolchain; a successful managed run alone does not prove that your native deployment was built.

How do I deserialize JSON without reflection in C#?

Use the corresponding JsonSerializer.Deserialize overload with the same generated type information. The full program does this immediately after serialization and checks transcript value equality. A null result is rejected by the example rather than being silently treated as an empty transcript.

The converter enforces the same schema and semantic rules as the explicit transcript methods. Missing required fields, invalid timing and unsupported schema versions are errors. Through System.Text.Json, invalid transcript documents raise JsonException, rather than the InvalidTranscriptJsonException used by Transcript.FromJson.

The optional resolver variation registers the generated context through JsonSerializerOptions.TypeInfoResolver. This is useful when code passes serializer options through a shared layer rather than accepting a type-info argument directly. Use the explicit type-info overload when you can: it makes the dependency easier to see and avoids accidentally selecting an unconfigured options instance.

Why does JSON serialization fail when reflection is disabled?

Disabling reflection does not automatically discover your application’s types. If you omit the context, System.Text.Json refuses the operation instead of falling back to a different transcript wire format. Add the generated metadata and use it for both reading and writing; do not re-enable reflection merely to hide missing registration.

All onespeech recipes