Last active
April 8, 2026 10:00
-
-
Save einarwh/93924fdd8a9ada23f12c3cf5d62531e4 to your computer and use it in GitHub Desktop.
Life in the JSON morgue.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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