Analisar json manualmente para classes POCO

Oct 06 2020

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 no CustomerSettingsobjeto da Holderclasse.
  • Da mesma forma, quero carregar todos os clientSettingsvalores no ClientSettingsobjeto da Holderclasse.
  • Da mesma forma, todos os productSettingsvalores em ProductSettingsobjeto de Holderclasse.
  • thothTestValores semelhantes em ThothTeste osirisTestvalores em OsirisTestobjeto.

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

6 PeterCsala Oct 06 2020 at 15:04

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

productSettings/entries

  • Aqui você pode usar ToObject<List<Mapping>
    • então você pode usar LINQ para construir uma lista de ProductSettingsMapping

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();