#c# #.net #asp.net-core
#c# #json
Вопрос:
Я хочу в общем случае сгладить некоторый json, чтобы я мог конвертировать в datatable и привязывать к datagrid с помощью c#
Каков наилучший способ сделать это, учитывая, что я не знаю, на сколько уровней я опускаюсь?
например
{
"appointmentid": 4,
"policyid": 1,
"guid": "00000000-0000-0000-0000-000000000000",
"number": "1234567890",
"ampm": "false",
"date": "2015-09-08T00:00:00",
"vehicle": {
"id": 1,
"guid": "00000000-0000-0000-0000-000000000000",
"make": null,
"model": null
},
"installer": {
"installerid": "1",
"name": "Installer 1",
"contact": "qwerty",
"qascore": "0",
"address1": "qwerty",
"address2": "qwerty",
"address3": null,
"address4": null,
"city": "qwertyu",
"county": "qwertyu",
"postcode": "asdfghj",
"country": "GB",
"email": "asdfghj",
"web": "asdfghjk",
"archived": false
},
"installations": [
{
"installationid": 6,
"installationstatus": {
"installationstatusid": 4,
"installationstatus": "FAIL"
},
"isactive": true
},
{
"installationid": 7,
"installationstatus": {
"installationstatusid": 1,
"installationstatus": "NEW"
},
"isactive": false
}
],
"archived": false
}
Я хотел бы расширить это (я полагаю, я мог бы выполнить итерацию по datatable, которую я преобразовал), вместо installations.1.installationid, я бы получил installationid1.
поскольку я собираюсь отображать результирующую таблицу данных в виде сетки, я хотел бы сохранить понятные имена столбцов.
Комментарии:
1. Здесь есть иерархия. Как вы ожидаете, что это будет как datatable?
2. Да, но это исключительно для просмотра в сетке. Столбцы выбираются на основе поля в базе данных, поэтому иерархия в данном случае не имеет значения
3. Ваш вопрос не очень хорошо сформулирован. Допустим, у вас был общий способ преобразовать приведенную выше иерархию JSON в таблицу данных. Какими были бы столбцы и строки, которые вы бы увидели в своей сетке?
4. Привет, Брайан. В идеале я хотел бы сохранить unque значения. Но, как я упоминал в конце, это может быть что-то вроде installations.1.installationid
Ответ №1:
Вы можете использовать Json.Net использует API-интерфейс LINQ-to-JSON Net для преобразования данных в JToken
структуру. Оттуда вы можете использовать рекурсивный вспомогательный метод, чтобы обойти структуру и сгладить ее до Dictionary<string, object>
, где ключи являются «путем» к каждому значению из исходного JSON. Я бы написал это примерно так:
public class JsonHelper
{
public static Dictionary<string, object> DeserializeAndFlatten(string json)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
JToken token = JToken.Parse(json);
FillDictionaryFromJToken(dict, token, "");
return dict;
}
private static void FillDictionaryFromJToken(Dictionary<string, object> dict, JToken token, string prefix)
{
switch (token.Type)
{
case JTokenType.Object:
foreach (JProperty prop in token.Children<JProperty>())
{
FillDictionaryFromJToken(dict, prop.Value, Join(prefix, prop.Name));
}
break;
case JTokenType.Array:
int index = 0;
foreach (JToken value in token.Children())
{
FillDictionaryFromJToken(dict, value, Join(prefix, index.ToString()));
index ;
}
break;
default:
dict.Add(prefix, ((JValue)token).Value);
break;
}
}
private static string Join(string prefix, string name)
{
return (string.IsNullOrEmpty(prefix) ? name : prefix "." name);
}
}
Используя этот DeserializeAndFlatten
метод с вашим JSON, вы в конечном итоге получите пары ключ-значение, подобные этой:
appointmentid: 4
policyid: 1
guid: 00000000-0000-0000-0000-000000000000
number: 1234567890
ampm: false
date: 9/8/2015 12:00:00 AM
vehicle.id: 1
vehicle.guid: 00000000-0000-0000-0000-000000000000
vehicle.make:
vehicle.model:
installer.installerid: 1
installer.name: Installer 1
installer.contact: qwerty
installer.qascore: 0
installer.address1: qwerty
installer.address2: qwerty
installer.address3:
installer.address4:
installer.city: qwertyu
installer.county: qwertyu
installer.postcode: asdfghj
installer.country: GB
installer.email: asdfghj
installer.web: asdfghjk
installer.archived: False
installations.0.installationid: 6
installations.0.installationstatus.installationstatusid: 4
installations.0.installationstatus.installationstatus: FAIL
installations.0.isactive: True
installations.1.installationid: 7
installations.1.installationstatus.installationstatusid: 1
installations.1.installationstatus.installationstatus: NEW
installations.1.isactive: False
archived: False
Если вы хотите сделать ключи более удобными для пользователя, вы могли бы использовать небольшие манипуляции со строками, чтобы сократить их. Может быть, что-то вроде этого:
var dict = JsonHelper.DeserializeAndFlatten(json);
foreach (var kvp in dict)
{
int i = kvp.Key.LastIndexOf(".");
string key = (i > -1 ? kvp.Key.Substring(i 1) : kvp.Key);
Match m = Regex.Match(kvp.Key, @".([0-9] ).");
if (m.Success) key = m.Groups[1].Value;
Console.WriteLine(key ": " kvp.Value);
}
Это дало бы вам этот результат вместо:
appointmentid: 4
policyid: 1
guid: 00000000-0000-0000-0000-000000000000
number: 1234567890
ampm: false
date: 9/8/2015 12:00:00 AM
id: 1
guid: 00000000-0000-0000-0000-000000000000
make:
model:
installerid: 1
name: Installer 1
contact: qwerty
qascore: 0
address1: qwerty
address2: qwerty
address3:
address4:
city: qwertyu
county: qwertyu
postcode: asdfghj
country: GB
email: asdfghj
web: asdfghjk
archived: False
installationid0: 6
installationstatusid0: 4
installationstatus0: FAIL
isactive0: True
installationid1: 7
installationstatusid1: 1
installationstatus1: NEW
isactive1: False
archived: False
Но обратите внимание, что при таком расположении вы потеряли некоторый контекст: например, вы можете видеть, что теперь есть два идентичных archived
ключа, тогда как в исходном JSON они были разными, потому что находились в разных частях иерархии ( installer.archived
против archived
). Вам нужно будет выяснить, как справиться с этой проблемой самостоятельно.
Скрипка:https://dotnetfiddle.net/gzhWHk
Комментарии:
1. спасибо за код и скрипку. Однако, один вопрос, если я передам массив назначений, то результирующий словарь будет иметь 0.appointmentid и т.д. Как бы мне лучше всего удалить это на верхнем уровне? после создания словаря, согласно примеру регулярного выражения? или с помощью метода сглаживания gerenic?
2. @брайан, твой подход намного элегантнее, чем mm ine!
3. @MatthewFlynn Я думаю, вам лучше манипулировать ключами постфактум, потому что рекурсивный метод в том виде, в каком он написан, требует, чтобы ключи были уникальными (в силу использования словаря для накопления значений). Не хочу сказать, что вы не смогли бы сделать это во время рекурсии, но если бы вы пошли по этому пути, вам нужно было бы изменить логику, чтобы иметь возможность обрабатывать дубликаты ключей.
4. Просто потрясающе… Спасибо. что, если бы это был список одного и того же объекта? прямо сейчас он вернет 0.appointmentid=1 …. 1.appointmentid =2 ,……… есть ли какой-либо способ сохранить их в массиве или списке?
Ответ №2:
Используя библиотеку Json.Net Вы могли бы использовать JSONPath $..*
, чтобы получить все элементы структуры JSON и отфильтровать те, у которых нет дочерних элементов, чтобы пропустить свойства контейнера.
например
var schemaObject = JObject.Parse(schema);
var values = schemaObject
.SelectTokens("$..*")
.Where(t => !t.HasValues)
.ToDictionary(t => t.Path, t => t.ToString());
Комментарии:
1. Это действительно работает, и оно проще и элегантнее; слава
2. Элегантное решение, спасибо
Ответ №3:
Другой вариант с использованием Json от Newtonsoft.ПОДКЛЮЧИТЕ LINQ к JSON для корневого объекта (то же самое можно сделать и с JArray
помощью):
var flattened = JObject.Parse(json)
.Descendants()
.OfType<JValue>()
.ToDictionary(jv => jv.Path, jv => jv.ToString())
Комментарии:
1. Это самый ясный ответ с результатом, который я искал.
Ответ №4:
Также искал решение, и оно выполнило работу за меня. Для результирующего набора использовались новые тупели C # 7. Если у кого-нибудь есть другое облегченное решение, мне интересно 🙂
async Task Main()
{
var jsonUsers = await new HttpClient().GetStringAsync(@"https://jsonplaceholder.typicode.com/users");
foreach (var flattedChild in GetFlatJsonChilds(JToken.Parse(jsonUsers)))
Console.WriteLine($"{flattedChild.path}: {flattedChild.value}");
}
IEnumerable<(string path, string value)> GetFlatJsonChilds(JToken token)
{
foreach (var child in token.Children())
{
if(token.Type != JTokenType.Array amp;amp;token.Children().First().Type != JTokenType.Property amp;amp; !child.Children().Any())
yield return (child.Path, child.ToString());
foreach(var childChild in GetFlatJsonChilds(child))
yield return childChild;
}
}
Результат для https://jsonplaceholder.typicode.com/users :
[0].id: 1
[0].name: Leanne Graham
[0].username: Bret
[0].email: Sincere@april.biz
[0].address.street: Kulas Light
[0].address.suite: Apt. 556
[0].address.city: Gwenborough
[0].address.zipcode: 92998-3874
[0].address.geo.lat: -37.3159
[0].address.geo.lng: 81.1496
[0].phone: 1-770-736-8031 x56442
[0].website: hildegard.org
[0].company.name: Romaguera-Crona
[0].company.catchPhrase: Multi-layered client-server neural-net
[0].company.bs: harness real-time e-markets
[1].id: 2
[1].name: Ervin Howell
[1].username: Antonette
[1].email: Shanna@melissa.tv
[1].address.street: Victor Plains
[1].address.suite: Suite 879
[1].address.city: Wisokyburgh
[1].address.zipcode: 90566-7771
[1].address.geo.lat: -43.9509
[1].address.geo.lng: -34.4618
[1].phone: 010-692-6593 x09125
[1].website: anastasia.net
[1].company.name: Deckow-Crist
[1].company.catchPhrase: Proactive didactic contingency
[1].company.bs: synergize scalable supply-chains
[2].id: 3
[2].name: Clementine Bauch
[2].username: Samantha
[2].email: Nathan@yesenia.net
[2].address.street: Douglas Extension
[2].address.suite: Suite 847
[2].address.city: McKenziehaven
[2].address.zipcode: 59590-4157
[2].address.geo.lat: -68.6102
[2].address.geo.lng: -47.0653
[2].phone: 1-463-123-4447
[2].website: ramiro.info
[2].company.name: Romaguera-Jacobson
[2].company.catchPhrase: Face to face bifurcated interface
[2].company.bs: e-enable strategic applications
[3].id: 4
[3].name: Patricia Lebsack
[3].username: Karianne
[3].email: Julianne.OConner@kory.org
[3].address.street: Hoeger Mall
[3].address.suite: Apt. 692
[3].address.city: South Elvis
[3].address.zipcode: 53919-4257
[3].address.geo.lat: 29.4572
[3].address.geo.lng: -164.2990
[3].phone: 493-170-9623 x156
[3].website: kale.biz
[3].company.name: Robel-Corkery
[3].company.catchPhrase: Multi-tiered zero tolerance productivity
[3].company.bs: transition cutting-edge web services
[4].id: 5
[4].name: Chelsey Dietrich
[4].username: Kamren
[4].email: Lucio_Hettinger@annie.ca
[4].address.street: Skiles Walks
[4].address.suite: Suite 351
[4].address.city: Roscoeview
[4].address.zipcode: 33263
[4].address.geo.lat: -31.8129
[4].address.geo.lng: 62.5342
[4].phone: (254)954-1289
[4].website: demarco.info
[4].company.name: Keebler LLC
[4].company.catchPhrase: User-centric fault-tolerant solution
[4].company.bs: revolutionize end-to-end systems
[5].id: 6
[5].name: Mrs. Dennis Schulist
[5].username: Leopoldo_Corkery
[5].email: Karley_Dach@jasper.info
[5].address.street: Norberto Crossing
[5].address.suite: Apt. 950
[5].address.city: South Christy
[5].address.zipcode: 23505-1337
[5].address.geo.lat: -71.4197
[5].address.geo.lng: 71.7478
[5].phone: 1-477-935-8478 x6430
[5].website: ola.org
[5].company.name: Considine-Lockman
[5].company.catchPhrase: Synchronised bottom-line interface
[5].company.bs: e-enable innovative applications
[6].id: 7
[6].name: Kurtis Weissnat
[6].username: Elwyn.Skiles
[6].email: Telly.Hoeger@billy.biz
[6].address.street: Rex Trail
[6].address.suite: Suite 280
[6].address.city: Howemouth
[6].address.zipcode: 58804-1099
[6].address.geo.lat: 24.8918
[6].address.geo.lng: 21.8984
[6].phone: 210.067.6132
[6].website: elvis.io
[6].company.name: Johns Group
[6].company.catchPhrase: Configurable multimedia task-force
[6].company.bs: generate enterprise e-tailers
[7].id: 8
[7].name: Nicholas Runolfsdottir V
[7].username: Maxime_Nienow
[7].email: Sherwood@rosamond.me
[7].address.street: Ellsworth Summit
[7].address.suite: Suite 729
[7].address.city: Aliyaview
[7].address.zipcode: 45169
[7].address.geo.lat: -14.3990
[7].address.geo.lng: -120.7677
[7].phone: 586.493.6943 x140
[7].website: jacynthe.com
[7].company.name: Abernathy Group
[7].company.catchPhrase: Implemented secondary concept
[7].company.bs: e-enable extensible e-tailers
[8].id: 9
[8].name: Glenna Reichert
[8].username: Delphine
[8].email: Chaim_McDermott@dana.io
[8].address.street: Dayna Park
[8].address.suite: Suite 449
[8].address.city: Bartholomebury
[8].address.zipcode: 76495-3109
[8].address.geo.lat: 24.6463
[8].address.geo.lng: -168.8889
[8].phone: (775)976-6794 x41206
[8].website: conrad.com
[8].company.name: Yost and Sons
[8].company.catchPhrase: Switchable contextually-based project
[8].company.bs: aggregate real-time technologies
[9].id: 10
[9].name: Clementina DuBuque
[9].username: Moriah.Stanton
[9].email: Rey.Padberg@karina.biz
[9].address.street: Kattie Turnpike
[9].address.suite: Suite 198
[9].address.city: Lebsackbury
[9].address.zipcode: 31428-2261
[9].address.geo.lat: -38.2386
[9].address.geo.lng: 57.2232
[9].phone: 024-648-3804
[9].website: ambrose.net
[9].company.name: Hoeger LLC
[9].company.catchPhrase: Centralized empowering task-force
[9].company.bs: target end-to-end models
Ответ №5:
Вот еще один способ сгладить JSON / преобразовать в DataTable с использованием Cinchoo ETL
Сгладить JSON:
using (var r = new ChoJSONReader("*** JSON file path ***"))
{
foreach (var rec in r.Select(f => f.Flatten()))
Console.WriteLine(rec.Dump());
}
JSON для преобразования в DataTable:
using (var r = new ChoJSONReader("*** JSON file path ***"))
{
var dt = r.AsDataTable();
Console.WriteLine(dt.DumpAsJson());
}
Ответ №6:
Десериализуем, затем LINQ выбирает для сглаживания. Я предполагаю, поскольку вы этого не указали, что вы хотите, чтобы вся информация о назначении и установщике содержалась в той же записи, что и конкретная установка?
Моей первоначальной идеей было бы использовать динамику, чтобы вы могли избежать необходимости вводить статические схемы для вашего JSON. Если у вас уже есть статические типы, которые могут действовать как схемы JSON, тогда вы можете избежать динамики (и всего, что это влечет за собой). Вот пример использования класса JSON.NET — чтобы проиллюстрировать, о чем я думаю:
public class DeserializeAndFlatten
{
public dynamic ParseJson()
{
var appointment = JObject.Parse(JsonData.JSON_TO_PARSE); // <-- replace the constant w/ the real JSON...
// this is where you flatten it all out!
// not going to put all the fields in, that would kill the example, LOL
var installations = appointment["installations"].Select(installation => new
{
appointmentId = appointment["appointmentid"],
policyId = appointment["policyid"],
vehicleId = appointment["vehicle"]["id"],
vehicleMake = appointment["vehicle"]["make"],
vehicleModel = appointment["vehicle"]["model"],
installerId = appointment["installer"]["installerid"],
installerName = appointment["installer"]["name"],
installationId = installation["installationid"],
installationStatus = installation["installationstatus"]["installationstatus"],
installationStatusId = installation["installationstatus"]["installationstatusid"],
}).ToList();
return installations;
}
}
Вы можете протестировать код:
static void Main(string[] args)
{
var jsonParser = new DeserializeAndFlatten();
var installations = jsonParser.ParseJson();
// FYI we get back a dynamic listing,
// so intellisense wont work...
foreach (var installation in installations)
{
Console.WriteLine($"appointmentId: {installation.appointmentId}");
Console.WriteLine($"installer: {installation.installerName}");
Console.WriteLine($"installation id: {installation.installationId}");
Console.WriteLine($"status: {installation.installationStatus}");
Console.WriteLine();
}
Console.ReadLine();
}
Комментарии:
1. Я пытаюсь создать универсальное решение. Так что, невозможно обобщенно сгладить его? Т.е. без понятия возвращаемых полей или глубины. Поскольку представление предназначено для использования в моем api, поэтому могут быть возвращены любые данные.
2. Это определенно возможно. Вместо указания полей по имени, вам пришлось бы перебирать все поля. Сложность будет заключаться в необходимости первоначального обхода иерархии данных JSON и определения глубины и родственных данных на разных глубинах. Существует так много умных способов сделать это, мне приходилось выполнять подобную работу раньше, за исключением того, что мой общий вывод был из JSON в электронную таблицу Excel. Приведенный мной пример должен стать достойной отправной точкой для начала изучения различных возможностей. Мир — это ваша устрица!
3. Я думал о возможном использовании чего-нибудь рекурсивного. Итак, возвращает ли jobject.parse элементы в структуре вложенного массива?
4. Да, это так. В основном это будет выглядеть как объект домена, представляющий JSON.
Ответ №7:
Для тех, кому нужно то же самое в F#:
module JsonFlatten =
let Join prefix name =
if String.IsNullOrEmpty(prefix) then name else prefix "." name
let rec FillDictionaryFromJToken (dict:Dictionary<string, string>) (token:JToken) (prefix:string) =
match token.Type with
| JTokenType.Object ->
for prop in token.Children<JProperty>() do
FillDictionaryFromJToken dict prop.Value (Join prefix prop.Name)
| JTokenType.Array ->
let mutable index = 0
for value in token.Children() do
FillDictionaryFromJToken dict value (Join prefix (index.ToString()))
index <- index 1
| _ ->
dict.Add(prefix, sprintf "%A" (token :?> JValue).Value)
let DeserializeAndFlatten(json:string) =
let dict = Dictionary<string, string>()
let token = JToken.Parse(json);
FillDictionaryFromJToken dict token ""
dict
Ответ №8:
Сегодня мне нужно было загрузить некоторый JSON в список ключей / значений, и я решил использовать конфигурацию Microsoft в .NET от «Microsoft.Расширения.Конфигурация»:
ConfigurationBuilder jsonConfigurationBuilder = new ConfigurationBuilder();
jsonConfigurationBuilder.AddJsonFile(fileName, false, false);
IConfiguration jsonConfiguration = jsonConfigurationBuilder.Build();
Теперь JSON загружен и проанализирован в IConfiguration, а затем его просто поместить в список пар ключ / значение, используя этот метод:
public IEnumerable<KeyValuePair<String, Object>> GetConfigurationEnumerator(IConfiguration configuration) {
// Get the configuration child sections into a stack.
Stack<IConfigurationSection> configurationSectionStack = new Stack<IConfigurationSection>();
foreach (IConfigurationSection configurationSection in configuration.GetChildren()) {
configurationSectionStack.Push(configurationSection);
}
// Return a key/value pair for each configuration section, and add additional child sections to the stack.
while (configurationSectionStack.Count > 0) {
// Get the configuration section.
IConfigurationSection configurationSection = configurationSectionStack.Pop();
// eturn a key/value pair.
yield return new KeyValuePair<String, Object>(configurationSection.Path, configurationSection.Value);
// Add the child sections to the stack.
foreach (IConfigurationSection configurationSectionChild in configurationSection.GetChildren()) {
configurationSectionStack.Push(configurationSectionChild);
}
}
} // GetConfigurationEnumerator
И запишите результат в консоль:
foreach (KeyValuePair<String, Object> value in GetConfigurationEnumerator(jsonConfiguration)) {
Console.WriteLine($" {value.Key} == {value.Value}");
}
Результат будет таким: «level0:level1:level2 == это значение»