Текущая версия каноникализатора JSON
This commit is contained in:
parent
9ae7538f5f
commit
e143ecd006
14
JsonCanicalizer.Test/JsonCanonicalizer.Test.csproj
Normal file
14
JsonCanicalizer.Test/JsonCanonicalizer.Test.csproj
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\JsonCanonicalizer.BL\JsonCanonicalizer.BL.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
26
JsonCanicalizer.Test/Program.cs
Normal file
26
JsonCanicalizer.Test/Program.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using JsonCanonicalizer.BL;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace JsonCanicalizer.Test;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
string input = File.ReadAllText(@"d:\Code\testbench\JsonCanonicalizer\JsonCanicalizer.Test\test.json");
|
||||||
|
|
||||||
|
using var doc = JsonDocument.Parse(input);
|
||||||
|
|
||||||
|
var writerOptions = new JsonWriterOptions
|
||||||
|
{
|
||||||
|
Indented = true,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
using var outputStream = new FileStream(@"d:\Code\testbench\JsonCanonicalizer\JsonCanicalizer.Test\result.json", FileMode.Create);
|
||||||
|
using var writer = new Utf8JsonWriter(outputStream, writerOptions);
|
||||||
|
|
||||||
|
Canonicalizer.WriteSorted(writer, doc.RootElement);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
JsonCanicalizer.Test/result.json
Normal file
64
JsonCanicalizer.Test/result.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"serviceTasks": [
|
||||||
|
{
|
||||||
|
"guid": "07e607a8-7673-458f-a8f0-2850f7864327",
|
||||||
|
"id": "Activity_1y13pct",
|
||||||
|
"name": "Внесение изменений/ дополнений/ исключений сведений в реестр уведомлений ТЭД (envelopeData)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "18b61142-b1e7-483a-bb17-3d42db1b8db3",
|
||||||
|
"id": "Activity_0lrt9vk",
|
||||||
|
"name": "Присвоение номера по шаблону"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "29203f14-7505-4994-bd84-574524b35c65",
|
||||||
|
"id": "Activity_086flld",
|
||||||
|
"name": "Получение выписки из реестра ТЭД"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "2a804098-fba1-4828-a139-c1449c3b2acf",
|
||||||
|
"id": "Activity_1etjhti",
|
||||||
|
"name": "Перевод дела в статус \"Положительное решение\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "406c7066-3eb7-4abd-a7d9-b9f96cc9e236",
|
||||||
|
"id": "Activity_0ptjle4",
|
||||||
|
"name": "Включение в реестр уведомлений ТЭД (envelopeData)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "59b09840-2b0f-47c3-9d07-941e00dc9794",
|
||||||
|
"id": "Activity_1wcy9l4",
|
||||||
|
"name": "Открытые сведения из ЕГРЮЛ (включая сведения, доступ к которым ограничен абзацами 3 и 5 пункта 1 статьи 6 Федерального закона от 08.08.2001 № 129-ФЗ) по запросам органов государственной власти (envelopeData)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "805fe785-5c2b-4ee2-a2e7-c4adbc40ae5d",
|
||||||
|
"id": "Activity_0bwwih4",
|
||||||
|
"name": "Перевод в статус \"Отказ в предоставлении услуги\" (4)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "80dbe463-1df6-4f9f-b76d-888786c1484f",
|
||||||
|
"id": "Activity_0ii8c0k",
|
||||||
|
"name": "Перевод дела в статус \"Отрицательное решение\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "87c1dc1d-b8d6-464c-8eff-48163f5b86ab",
|
||||||
|
"id": "Activity_0hwnhm8",
|
||||||
|
"name": "Отправка на ЕПГУ \"Услуга оказана\" (3) с положительным решением, необходимые файлы получены"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "bee4c89c-601f-4211-a615-7ef8a3eeade7",
|
||||||
|
"id": "Activity_14vp8wt",
|
||||||
|
"name": "Получение данных из реестра уведомлений ТЭД (envelopeData)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "e51e3b7c-082c-4555-bbfa-1579c81b9ffb",
|
||||||
|
"id": "Activity_0ux39aa",
|
||||||
|
"name": "Перевод статуса заявления на ЕПГУ \"Заявление принято к рассмотрению\"(6)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "ee174972-ab78-4fc6-979a-37c1384f3c6d",
|
||||||
|
"id": "Activity_1yii4fr",
|
||||||
|
"name": "Сведения из ЕГРИП по запросам органов государственной власти (открытые сведения и сведения о рождении и адресе ИП) (регламентный)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
JsonCanicalizer.Test/test.json
Normal file
1
JsonCanicalizer.Test/test.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"serviceTasks":[{"guid":"18b61142-b1e7-483a-bb17-3d42db1b8db3","id":"Activity_0lrt9vk","name":"Присвоение номера по шаблону"},{"guid":"29203f14-7505-4994-bd84-574524b35c65","id":"Activity_086flld","name":"Получение выписки из реестра ТЭД"},{"guid":"80dbe463-1df6-4f9f-b76d-888786c1484f","id":"Activity_0ii8c0k","name":"Перевод дела в статус \"Отрицательное решение\""},{"guid":"805fe785-5c2b-4ee2-a2e7-c4adbc40ae5d","id":"Activity_0bwwih4","name":"Перевод в статус \"Отказ в предоставлении услуги\" (4)"},{"guid":"87c1dc1d-b8d6-464c-8eff-48163f5b86ab","id":"Activity_0hwnhm8","name":"Отправка на ЕПГУ \"Услуга оказана\" (3) с положительным решением, необходимые файлы получены"},{"guid":"2a804098-fba1-4828-a139-c1449c3b2acf","id":"Activity_1etjhti","name":"Перевод дела в статус \"Положительное решение\""},{"guid":"e51e3b7c-082c-4555-bbfa-1579c81b9ffb","id":"Activity_0ux39aa","name":"Перевод статуса заявления на ЕПГУ \"Заявление принято к рассмотрению\"(6)"},{"guid":"ee174972-ab78-4fc6-979a-37c1384f3c6d","id":"Activity_1yii4fr","name":"Сведения из ЕГРИП по запросам органов государственной власти (открытые сведения и сведения о рождении и адресе ИП) (регламентный)"},{"guid":"59b09840-2b0f-47c3-9d07-941e00dc9794","id":"Activity_1wcy9l4","name":"Открытые сведения из ЕГРЮЛ (включая сведения, доступ к которым ограничен абзацами 3 и 5 пункта 1 статьи 6 Федерального закона от 08.08.2001 № 129-ФЗ) по запросам органов государственной власти (envelopeData)"},{"guid":"406c7066-3eb7-4abd-a7d9-b9f96cc9e236","id":"Activity_0ptjle4","name":"Включение в реестр уведомлений ТЭД (envelopeData)"},{"guid":"bee4c89c-601f-4211-a615-7ef8a3eeade7","id":"Activity_14vp8wt","name":"Получение данных из реестра уведомлений ТЭД (envelopeData)"},{"guid":"07e607a8-7673-458f-a8f0-2850f7864327","id":"Activity_1y13pct","name":"Внесение изменений/ дополнений/ исключений сведений в реестр уведомлений ТЭД (envelopeData)"}]}
|
||||||
112
JsonCanonicalizer.BL/Canonicalizer.cs
Normal file
112
JsonCanonicalizer.BL/Canonicalizer.cs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
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()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
9
JsonCanonicalizer.BL/JsonCanonicalizer.BL.csproj
Normal file
9
JsonCanonicalizer.BL/JsonCanonicalizer.BL.csproj
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
37
JsonCanonicalizer.sln
Normal file
37
JsonCanonicalizer.sln
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.14.36915.13
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonCanonicalizer", "JsonCanonicalizer\JsonCanonicalizer.csproj", "{35AE0B30-4ACC-4CCF-9FC7-43A7DBF86B54}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonCanonicalizer.BL", "JsonCanonicalizer.BL\JsonCanonicalizer.BL.csproj", "{C4C72D1B-E361-49FB-A76D-6691D27CF2AB}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonCanonicalizer.Test", "JsonCanicalizer.Test\JsonCanonicalizer.Test.csproj", "{E43C66DF-FE35-4752-8201-A08E807122DB}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{35AE0B30-4ACC-4CCF-9FC7-43A7DBF86B54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{35AE0B30-4ACC-4CCF-9FC7-43A7DBF86B54}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{35AE0B30-4ACC-4CCF-9FC7-43A7DBF86B54}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{35AE0B30-4ACC-4CCF-9FC7-43A7DBF86B54}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C4C72D1B-E361-49FB-A76D-6691D27CF2AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C4C72D1B-E361-49FB-A76D-6691D27CF2AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C4C72D1B-E361-49FB-A76D-6691D27CF2AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C4C72D1B-E361-49FB-A76D-6691D27CF2AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E43C66DF-FE35-4752-8201-A08E807122DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E43C66DF-FE35-4752-8201-A08E807122DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E43C66DF-FE35-4752-8201-A08E807122DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E43C66DF-FE35-4752-8201-A08E807122DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {ADE14663-0DC6-43E9-9161-DC0A59CE0881}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
14
JsonCanonicalizer/JsonCanonicalizer.csproj
Normal file
14
JsonCanonicalizer/JsonCanonicalizer.csproj
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\JsonCanonicalizer.BL\JsonCanonicalizer.BL.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
41
JsonCanonicalizer/Program.cs
Normal file
41
JsonCanonicalizer/Program.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace JsonCanonicalizer
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var inputStream = Console.OpenStandardInput();
|
||||||
|
using var doc = JsonDocument.Parse(inputStream);
|
||||||
|
|
||||||
|
var writerOptions = new JsonWriterOptions
|
||||||
|
{
|
||||||
|
Indented = true,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
using var outputStream = Console.OpenStandardOutput();
|
||||||
|
using var writer = new Utf8JsonWriter(outputStream, writerOptions);
|
||||||
|
|
||||||
|
BL.Canonicalizer.WriteSorted(writer, doc.RootElement);
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
Console.Out.WriteLine();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Invalid JSON: {ex.Message}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Error: {ex.Message}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
nuget.config
Normal file
7
nuget.config
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
Loading…
Reference in New Issue
Block a user