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 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")); } } /// /// Универсально извлекает значение guid как строку для сравнения. /// Работает даже если guid почему-то оказался числом или булевым. /// 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() }; } }