Skip to content

Instantly share code, notes, and snippets.

@einarwh
Last active April 8, 2026 10:00
Show Gist options
  • Select an option

  • Save einarwh/93924fdd8a9ada23f12c3cf5d62531e4 to your computer and use it in GitHub Desktop.

Select an option

Save einarwh/93924fdd8a9ada23f12c3cf5d62531e4 to your computer and use it in GitHub Desktop.
Life in the JSON morgue.
void Main()
{
var john1 = new CustomerDto20260101
{
UserId = "john-1",
Email = "john.fst@foomail.com",
BirthDate = new DateTime(2001, 01, 01),
};
var john2 = new CustomerDto20260202
{
UserId = "john-2",
Email = "john.snd@foomail.com",
BirthDate = new DateOnly(2002, 02, 02),
DisplayName = "John the 2nd"
};
var john3 = new CustomerDto20260303
{
UserName = "john-3",
Email = "john.trd@foomail.com",
BirthDate = new DateOnly(2003, 03, 03),
DisplayName = "Johnny 33",
Items = [ "faithful dog", "can of beans", "shotgun" ]
};
Write(john1, CustomerDto20260101.SchemaName, "john-1.json", true);
Write(john2, CustomerDto20260202.SchemaName, "john-2.json", true);
Write(john3, CustomerDto20260303.SchemaName, "john-3.json", true);
Read("john-1.json");
Read("john-2.json");
Read("john-3.json");
}
public static string ToFullPath(string path)
{
return $"/Users/einarwh/Downloads/hellish-enterprise/{path}";
}
public static void Write(object obj, string schemaName, string fileName, bool validate)
{
var result = Serialize(obj, schemaName, validate);
File.WriteAllText(ToFullPath(fileName), result);
result.Dump();
}
public static void Read(string fileName)
{
Deserialize(File.ReadAllText(ToFullPath(fileName))).Dump();
}
public static CustomerDto20260303 Deserialize(string jsonText)
{
var root = JsonSerializer.Deserialize<JsonElement>(jsonText);
var schema = root.GetProperty("$schema").GetString();
return schema switch
{
CustomerDto20260303.SchemaName => root.Deserialize<CustomerDto20260303>(GetSerializerOptions()),
CustomerDto20260202.SchemaName => Upgrade(root.Deserialize<CustomerDto20260202>(GetSerializerOptions())),
CustomerDto20260101.SchemaName => Upgrade(Upgrade(root.Deserialize<CustomerDto20260101>(GetSerializerOptions()))),
_ => throw new Exception($"Unsupported schema {schema}")
};
}
public static CustomerDto20260202 Upgrade(CustomerDto20260101 previous) =>
new CustomerDto20260202
{
UserId = previous.UserId,
Email = previous.Email,
BirthDate = DateOnly.FromDateTime(previous.BirthDate),
DisplayName = previous.UserId.ToUpper(),
};
public static CustomerDto20260303 Upgrade(CustomerDto20260202 previous) =>
new CustomerDto20260303
{
UserName = previous.UserId,
Email = previous.Email,
BirthDate = previous.BirthDate,
DisplayName = previous.DisplayName,
Items = []
};
public static string Serialize(object obj, string schemaName, bool validate)
{
var jsonDoc = JsonSerializer.SerializeToDocument(obj, obj.GetType(), GetSerializerOptions());
var jsonObj = JsonObject.Create(jsonDoc.RootElement);
jsonObj.Add("$schema", schemaName);
if (validate)
{
var schema = JsonSchema.FromFile(ToFullPath(schemaName), GetBuildOptions());
var evaluation = schema.Evaluate(jsonDoc.RootElement, GetEvaluationOptions());
if (!evaluation.IsValid) {
evaluation.Dump();
}
var assembly = Assembly.GetAssembly(typeof(JsonSchema));
var fileVersion =
assembly
.GetCustomAttribute<AssemblyFileVersionAttribute>()?
.Version;
var validatorName = $"{assembly.GetName().Name}-{fileVersion}";
var toolObj = new JsonObject();
toolObj.Add("tool", validatorName);
toolObj.Add("timestamp", DateTime.UtcNow);
toolObj.Add("result", evaluation.IsValid);
jsonObj.Add("$validation", toolObj);
}
return jsonObj.ToString();
}
public static JsonSerializerOptions GetSerializerOptions() =>
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
};
public static BuildOptions GetBuildOptions() =>
new BuildOptions
{
Dialect = Dialect.Draft202012,
SchemaRegistry = new(),
DialectRegistry = new(),
VocabularyRegistry = new()
};
public static EvaluationOptions GetEvaluationOptions() =>
new EvaluationOptions
{
OutputFormat = OutputFormat.List,
RequireFormatValidation = true,
PreserveDroppedAnnotations = true,
};
public class CustomerDto20260101
{
public const string SchemaName = "customer-schema-20260101.json";
public string UserId { get; set; }
public string Email { get; set; }
public DateTime BirthDate { get; set; }
}
public class CustomerDto20260202
{
public const string SchemaName = "customer-schema-20260202.json";
public string UserId { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public DateOnly BirthDate { get; set; }
}
public class CustomerDto20260303
{
public const string SchemaName = "customer-schema-20260303.json";
public string UserName { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public DateOnly BirthDate { get; set; }
public List<string> Items { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment