Analisar json manualmente para classes POCO
Tenho abaixo de json que preciso desserializar em C#-
{
"clientSettings":[
{
"clientId":12345,
"entries":[
{
"key":"abc",
"value":false
},
{
"key":"def",
"value":false
},
{
"key":"ghi",
"value":false
}
]
},
{
"clientId":9876,
"entries":[
{
"key":"lkmn",
"value":false
}
]
}
],
"productSettings":[
{
"productId":11,
"entries":[
{
"key":"jkl",
"value":true
},
{
"key":"mno",
"value":true
}
]
},
{
"productId":12,
"entries":[
{
"key":"jkl",
"value":true
},
{
"key":"mno",
"value":true
}
]
}
],
"customerSettings":[
{
"key":"enableData",
"value":false
},
{
"key":"minPriceValue",
"value":"10.28"
},
{
"key":"presentData",
"value":"AEGIS"
}
],
"thothTest":{
"9876":[
"K"
],
"5431":[
"A",
"L"
],
"5123":[
"L"
]
},
"osirisTest":{
"7678":[
"K"
]
}
}
Abaixo estão as classes que criei para deserialzie json em -
public class ProcessHolder : Holder
{
public IDictionary<int, ISet<string>> OsirisTest { get; set; }
public IDictionary<int, ISet<string>> ThothTest { get; set; }
}
public class Holder
{
public IList<Mapping> CustomerSettings { get; set; }
public IList<ClientSettingsMapping> ClientSettings { get; set; }
public IList<ProductSettingsMapping> ProductSettings { get; set; }
}
public class Mapping
{
public string Key { get; set; }
public object Value { get; set; }
}
public class ProductSettingsMapping : Mapping
{
public int ProductId { get; set; }
}
public class ClientSettingsMapping : Mapping
{
public int ClientId { get; set; }
}
- Eu quero carregar todos os
customerSettingsvalores noCustomerSettingsobjeto daHolderclasse. - Da mesma forma, quero carregar todos os
clientSettingsvalores noClientSettingsobjeto daHolderclasse. - Da mesma forma, todos os
productSettingsvalores emProductSettingsobjeto deHolderclasse. thothTestValores semelhantes emThothTesteosirisTestvalores emOsirisTestobjeto.
Nota: As classes POCO acima costumavam trabalhar com o formato json antigo e estávamos desserializando-o diretamente usando a biblioteca JSON Newton, mas por alguma razão interna estamos trabalhando no redesenho do json em novo json, que é o que compartilhei acima em minha pergunta. E agora, com esse novo design json, não podemos desserializá-lo diretamente para minhas classes POCO acima, então é por isso que vim abaixo o código para fazer isso manualmente. Como as classes POCO acima são usadas em tantos lugares, é por isso que não quero tocar nessa interface.
Abaixo está o meu código de trabalho que analisa manualmente esse json e preenche as classes acima. Eu queria ver se há alguma maneira de melhorarmos a base de código abaixo, pois parece que estou repetindo muitas coisas. Acho que pode ser organizado com alguns métodos auxiliares que podem fazer o trabalho para mim, mas não conseguem descobrir como fazer isso.
public static void Main(string[] args)
{
var jsonContent = File.ReadAllText("/beta/Downloads/test.json");
var parsed = JObject.Parse(jsonContent);
var parsedClientSettings = parsed["clientSettings"];
List<ClientSettingsMapping> clientSettings = new List<ClientSettingsMapping>();
foreach (var parsedClientSetting in parsedClientSettings)
{
var clientId = parsedClientSetting.Value<int>("clientId");
foreach (var entry in parsedClientSetting["entries"])
{
clientSettings.Add(new ClientSettingsMapping { ClientId = clientId, Key = entry["key"].ToString(), Value = entry["value"].ToString() });
}
}
var parsedproductSettings = parsed["productSettings"];
List<ProductSettingsMapping> productSettings = new List<ProductSettingsMapping>();
foreach (var parsedproductSetting in parsedproductSettings)
{
var productId = parsedproductSetting.Value<int>("productId");
foreach (var entry in parsedproductSetting["entries"])
{
productSettings.Add(new ProductSettingsMapping { ProductId = productId, Key = entry["key"].ToString(), Value = entry["value"].ToString() });
}
}
var parsedCustomerSettings = parsed["customerSettings"];
List<Mapping> customerSettings = new List<Mapping>();
foreach (var entry in parsedCustomerSettings)
{
customerSettings.Add(new Mapping { Key = entry["key"].ToString(), Value = entry["value"].ToString() });
}
var parsedthothTests = parsed["thothTest"];
IDictionary<int, ISet<string>> thothTest = new Dictionary<int, ISet<string>>();
foreach (JProperty x in (JToken)parsedthothTests)
{
int name = int.Parse(x.Name);
JToken value = x.Value;
ISet<string> values = new HashSet<string>();
foreach (var val in value)
{
values.Add((string)val);
}
thothTest.Add(name, values);
}
var parsedOsirisTests = parsed["osirisTest"];
IDictionary<int, ISet<string>> osirisTest = new Dictionary<int, ISet<string>>();
foreach (JProperty x in (JToken)parsedOsirisTests)
{
int name = int.Parse(x.Name);
JToken value = x.Value;
ISet<string> values = new HashSet<string>();
foreach (var val in value)
{
values.Add((string)val);
}
osirisTest.Add(name, values);
}
ProcessHolder processHolder = new ProcessHolder() { ClientSettings = clientSettings, ProductSettings = productSettings, CustomerSettings = customerSettings, ThothTest = thothTest, OsirisTest = osirisTest };
// now use "processHolder" object here
}
Respostas
Você pode aproveitar as vantagens do método ToObject () do JToken .
Com isso, sua lógica de análise pode ser simplificada para:
var jsonContent = File.ReadAllText("/beta/Downloads/test.json");
var parsed = JObject.Parse(jsonContent);
//ClientSettings needs special handling
var parsedClientSettings = parsed["clientSettings"];
var clientSettings = new List<ClientSettingsMapping>();
foreach (var parsedClientSetting in parsedClientSettings)
{
var clientId = parsedClientSetting.Value<int>("clientId");
var entries = parsedClientSetting["entries"].ToObject<List<Mapping>>();
clientSettings.AddRange(entries
.Select(entry => new ClientSettingsMapping { ClientId = clientId, Key = entry.Key, Value = entry.Value }));
}
//ProductSettings needs special handling
var parsedproductSettings = parsed["productSettings"];
var productSettings = new List<ProductSettingsMapping>();
foreach (var parsedproductSetting in parsedproductSettings)
{
var productId = parsedproductSetting.Value<int>("productId");
var entries = parsedproductSetting["entries"].ToObject<List<Mapping>>();
productSettings.AddRange(entries
.Select(entry => new ProductSettingsMapping {ProductId = productId, Key = entry.Key, Value = entry.Value}));
}
var processHolder = new ProcessHolder
{
ClientSettings = clientSettings,
ProductSettings = productSettings,
CustomerSettings = parsed["customerSettings"].ToObject<List<Mapping>>(),
ThothTest = parsed["thothTest"].ToObject<Dictionary<int, ISet<string>>>(),
OsirisTest = parsed["osirisTest"].ToObject<Dictionary<int, ISet<string>>>()
};
clientSettings/entries
- Aqui você pode usar
ToObject<List<Mapping>- então você pode usar LINQ para construir uma lista de
ClientSettingsMapping
- então você pode usar LINQ para construir uma lista de
productSettings/entries
- Aqui você pode usar
ToObject<List<Mapping>- então você pode usar LINQ para construir uma lista de
ProductSettingsMapping
- então você pode usar LINQ para construir uma lista de
customerSettings
- Aqui você pode usar
ToObject<List<Mapping>
thothTest e osirisTest
- Aqui você pode usar
ToObject<Dictionary<int, ISet<string>>>
ATUALIZAÇÃO # 1 : Adicionar método auxiliar para reduzir a duplicação de código
A fim de reduzir a duplicação de código (as configurações do cliente e do produto são tratadas quase da mesma forma), podemos introduzir o seguinte método auxiliar:
private List<T> ParseSettings<T>(JToken parsed, string settingsNodeName, string idNodeName, Func<(int id, string key, object value), T> mapper)
{
var rawSettings = parsed[settingsNodeName];
var settings = new List<T>();
foreach (var setting in rawSettings)
{
var id = setting.Value<int>(idNodeName);
var entries = setting["entries"].ToObject<List<Mapping>>();
settings.AddRange(entries
.Select(entry => mapper((id, entry.Key, entry.Value))));
}
return settings;
}
Seu uso seria assim:
var productSettings = ParseSettings(parsed, "productSettings", "productId",
mapping=> new ProductSettingsMapping {ProductId = mapping.id, Key = mapping.key, Value = mapping.value});
var clientSettings = ParseSettings(parsed, "clientSettings", "clientId",
mapping=> new ClientSettingsMapping {ClientId = mapping.id, Key = mapping.key, Value = mapping.value});
ATUALIZAÇÃO 2 : Adicionando uma implementação alternativa (usa sintaxe de expressão de consulta linq ) para ParseSettings:
private static List<T> ParseSettings<T>(JToken parsed, string settingsNodeName, string idNodeName,
Func<(int id, string key, object value), T> mapper)
=>
(from setting in parsed[settingsNodeName]
let id = setting.Value<int>(idNodeName)
let entries = setting["entries"].ToObject<List<Mapping>>()
from entry in entries
select mapper((id, entry.Key, entry.Value)))
.ToList();