forked from urbrato/json-canonicalizer
113 lines
3.9 KiB
C#
113 lines
3.9 KiB
C#
using System.Runtime.Serialization;
|
||
using System.Text.Json;
|
||
|
||
namespace JsonCanonicalizer.BL;
|
||
|
||
public class Canonicalizer
|
||
{
|
||
public static void WriteSorted(Utf8JsonWriter writer, JsonElement element)
|
||
{
|
||
switch (element.ValueKind)
|
||
{
|
||
case JsonValueKind.Object:
|
||
writer.WriteStartObject();
|
||
// Сортируем ключи объекта по имени
|
||
foreach (var property in element.EnumerateObject()
|
||
.OrderBy(p => p.Name, StringComparer.Ordinal))
|
||
{
|
||
writer.WritePropertyName(property.Name);
|
||
WriteSorted(writer, property.Value);
|
||
}
|
||
writer.WriteEndObject();
|
||
break;
|
||
|
||
case JsonValueKind.Array:
|
||
writer.WriteStartArray();
|
||
var items = element.EnumerateArray().ToList();
|
||
|
||
// Проверяем, нужно ли сортировать массив по guid:
|
||
// 1. Все элементы — объекты
|
||
// 2. У каждого объекта есть свойство "guid"
|
||
bool allAreObjects = items.All(i => i.ValueKind == JsonValueKind.Object);
|
||
bool allHaveGuid = allAreObjects &&
|
||
items.All(i => IsOrderable(i));
|
||
|
||
IEnumerable<JsonElement> orderedItems = items;
|
||
if (allHaveGuid)
|
||
{
|
||
orderedItems = items.OrderBy(i => GetOrder(i),
|
||
StringComparer.OrdinalIgnoreCase);
|
||
}
|
||
|
||
// Рекурсивно обрабатываем каждый элемент (на случай вложенных массивов)
|
||
foreach (var item in orderedItems)
|
||
{
|
||
WriteSorted(writer, item);
|
||
}
|
||
writer.WriteEndArray();
|
||
break;
|
||
|
||
case JsonValueKind.String:
|
||
writer.WriteStringValue(element.GetString());
|
||
break;
|
||
|
||
case JsonValueKind.Number:
|
||
if (element.TryGetDecimal(out decimal dec))
|
||
writer.WriteNumberValue(dec);
|
||
else
|
||
writer.WriteNumberValue(element.GetDouble());
|
||
break;
|
||
|
||
case JsonValueKind.True:
|
||
writer.WriteBooleanValue(true);
|
||
break;
|
||
|
||
case JsonValueKind.False:
|
||
writer.WriteBooleanValue(false);
|
||
break;
|
||
|
||
case JsonValueKind.Null:
|
||
writer.WriteNullValue();
|
||
break;
|
||
|
||
default:
|
||
writer.WriteCommentValue(element.GetRawText());
|
||
break;
|
||
}
|
||
}
|
||
|
||
static bool IsOrderable(JsonElement e)
|
||
{
|
||
return e.TryGetProperty("id", out _) || e.TryGetProperty("guid", out _);
|
||
}
|
||
|
||
static string GetOrder(JsonElement e)
|
||
{
|
||
if (e.TryGetProperty("id", out JsonElement id))
|
||
{
|
||
return GetGuidValue(id);
|
||
}
|
||
else
|
||
{
|
||
return GetGuidValue(e.GetProperty("guid"));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Универсально извлекает значение guid как строку для сравнения.
|
||
/// Работает даже если guid почему-то оказался числом или булевым.
|
||
/// </summary>
|
||
static string GetGuidValue(JsonElement guidElement)
|
||
{
|
||
return guidElement.ValueKind switch
|
||
{
|
||
JsonValueKind.String => guidElement.GetString() ?? "",
|
||
JsonValueKind.Number => guidElement.GetRawText(),
|
||
JsonValueKind.True => "true",
|
||
JsonValueKind.False => "false",
|
||
JsonValueKind.Null => "",
|
||
_ => guidElement.GetRawText()
|
||
};
|
||
}
|
||
}
|