Last active
October 9, 2024 03:34
-
-
Save jasonswearingen/3394f32a8a962873cbf911b95d241584 to your computer and use it in GitHub Desktop.
snippet to deserialize json5 using system.text.json
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
//this is a snippet from my framework. you'll have to make some tiny syntax adjustments to use this. | |
//for details on json5, see https://json5.org/ | |
/// <summary> | |
/// Preprocess a JSON5 string to convert it to valid JSON for Microsoft's System.Text.Json deserializer. | |
/// </summary> | |
/// <param name="json5String">The JSON5 string to preprocess.</param> | |
/// <returns>A valid JSON string.</returns> | |
private static string PreprocessJson5ToJson(string json5String) | |
{ | |
// Handle multi-line strings with backslash-newline | |
json5String = @"\\\r?\n\s*"._ToRegex().Replace(json5String, ""); | |
// Convert unquoted keys to quoted keys (supports ECMAScript 5.1 IdentifierName) | |
json5String = @"(^|[{,\s])([$_\p{L}][$_\p{L}\p{Nd}]*)\s*:"._ToRegex().Replace(json5String, "$1\"$2\":"); | |
// Remove leading plus sign from numbers (simplified version) | |
json5String = @"([^\w\.\-])\+(\d+(\.\d*)?([eE][+\-]?\d+)?)"._ToRegex().Replace(json5String, "$1$2"); | |
// Add leading zero to numbers starting with decimal point (simplified version) | |
json5String = @"([^\w\.])\.(\d+([eE][+\-]?\d+)?)"._ToRegex().Replace(json5String, "$10.$2"); | |
// Add trailing zero to numbers ending with decimal point (simplified version without negative lookahead) | |
json5String = @"(\d+)\.(\s|,|\}|\])"._ToRegex().Replace(json5String, "$1.0$2"); | |
// Convert hexadecimal numbers to decimal | |
json5String = @"0x[0-9a-fA-F]+"._ToRegex().Replace(json5String, m => Convert.ToInt64(m.Value, 16).ToString()); | |
// Convert single-quoted strings to double-quoted strings, handling escapes (simplified version) | |
json5String = @"'([^'\\]*(\\.[^'\\]*)*)'"._ToRegex().Replace(json5String, m => "\"" + m.Groups[1].Value.Replace("\"", "\\\"") + "\""); | |
// Convert NaN, Infinity, -Infinity to strings without positive lookahead. will be converted back to number by our `NumberHandlingConverter` converter in a later step | |
json5String = @"([:,\s\[\{])\s*(NaN|Infinity|-Infinity)(\s|,|\]|\})"._ToRegex().Replace(json5String, m => m.Groups[1].Value + "\"" + m.Groups[2].Value + "\"" + m.Groups[3].Value); | |
return json5String; | |
} | |
/// <summary> | |
/// deserialize a json file using json5, which is less strict about json formatting | |
/// </summary> | |
/// <typeparam name="TJsonSerialized"></typeparam> | |
/// <param name="json5ResFilePath"></param> | |
/// <returns></returns> | |
public static TJsonSerialized DeserializeJson5<TJsonSerialized>(string json5String) | |
{ | |
//dotnet, doesn't support unquoted keys | |
var jsonString = PreprocessJson5ToJson(json5String); | |
JsonSerializerOptions jsonOptions = new() | |
{ | |
AllowTrailingCommas = true, | |
IncludeFields = true, | |
//MaxDepth = 10, | |
ReadCommentHandling = JsonCommentHandling.Skip, | |
PropertyNameCaseInsensitive = true, | |
Converters = { new CaseInsensitiveEnumConverter(), new NumberHandlingConverter() }, | |
//Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter(System.Text.Json.JsonNamingPolicy.CamelCase, allowIntegerValues: true) } | |
}; | |
//var jsonString = FileAccess.Open(match, FileAccess.ModeFlags.Read).GetAsText(); | |
var jsonFile = JsonSerializer.Deserialize<TJsonSerialized>(jsonString, jsonOptions); | |
return jsonFile; | |
} | |
} | |
/// <summary> | |
/// Custom converter to handle special number values like Infinity, -Infinity, and NaN. | |
/// </summary> | |
internal class NumberHandlingConverter : JsonConverter<double> | |
{ | |
public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | |
{ | |
if (reader.TokenType == JsonTokenType.String) | |
{ | |
string value = reader.GetString(); | |
return value.ToLower() switch | |
{ | |
"infinity" => double.PositiveInfinity, | |
"-infinity" => double.NegativeInfinity, | |
"nan" => double.NaN, | |
_ => throw new JsonException($"Unable to convert \"{value}\" to a valid double.") | |
}; | |
} | |
else if (reader.TokenType == JsonTokenType.Number) | |
{ | |
return reader.GetDouble(); | |
} | |
throw new JsonException(); | |
} | |
public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) | |
{ | |
if (double.IsPositiveInfinity(value)) | |
{ | |
writer.WriteStringValue("Infinity"); | |
} | |
else if (double.IsNegativeInfinity(value)) | |
{ | |
writer.WriteStringValue("-Infinity"); | |
} | |
else if (double.IsNaN(value)) | |
{ | |
writer.WriteStringValue("NaN"); | |
} | |
else | |
{ | |
writer.WriteNumberValue(value); | |
} | |
} | |
} | |
/// <summary> | |
/// Custom converter to handle case-insensitive deserialization of enums. | |
/// </summary> | |
internal class CaseInsensitiveEnumConverter : JsonConverterFactory | |
{ | |
public override bool CanConvert(Type typeToConvert) | |
{ | |
return typeToConvert.IsEnum; | |
} | |
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) | |
{ | |
var converterType = typeof(CaseInsensitiveEnumConverter<>).MakeGenericType(typeToConvert); | |
return (JsonConverter)Activator.CreateInstance(converterType); | |
} | |
} | |
internal class CaseInsensitiveEnumConverter<T> : JsonConverter<T> where T : struct, Enum | |
{ | |
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | |
{ | |
if (reader.TokenType != JsonTokenType.String) | |
{ | |
throw new JsonException(); | |
} | |
string enumValue = reader.GetString(); | |
if (Enum.TryParse(enumValue, ignoreCase: true, out T result)) | |
{ | |
return result; | |
} | |
throw new JsonException($"Unable to convert \"{enumValue}\" to Enum \"{typeof(T)}\"."); | |
} | |
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) | |
{ | |
writer.WriteStringValue(value.ToString()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment