juryvorpopen
juryvorpopen

Reputation: 33

Deserialize JSON with unknown value name and get said name in C#

I'm trying to deserialize a JSON response from an API and get the NAME of what in this example is "af" (country code of Afghanistan) inside "iso", but the name of the field will be different everytime based on the country code, how can i read the name of the field and retrieve each country code in the api response? thanks!

Example response:

{
   "afghanistan":{
      "iso":{
         "af":1
      },
      "prefix":{
         "+93":1
      },
      "text_en":"Afghanistan",
      "text_ru":"Афганистан",
      "virtual21":{
         "activation":1
      },
      "virtual23":{
         "activation":1
      },
      "virtual29":{
         "activation":1
      },
      "virtual30":{
         "activation":1
      },
      "virtual31":{
         "activation":1
      },
      "virtual32":{
         "activation":1
      },
      "virtual4":{
         "activation":1
      }
   }
}

This goes on for a very large list of countries

this is how i am currently deserializing:

  var response = JsonConvert.DeserializeObject<Dictionary<string, ResponseFields>>(json);

And this is the VS-generated ResponseFields class:

public class ResponseFields
{
    public Iso iso { get; set; }
    public Prefix prefix { get; set; }
    public string text_en { get; set; }
    public string text_ru { get; set; }
    public Virtual21 virtual21 { get; set; }
    public Virtual23 virtual23 { get; set; }
    public Virtual29 virtual29 { get; set; }
    public Virtual30 virtual30 { get; set; }
    public Virtual31 virtual31 { get; set; }
    public Virtual32 virtual32 { get; set; }
    public Virtual4 virtual4 { get; set; }
}

public class Iso
{
    public int af { get; set; }
}

public class Prefix
{
    public int _93 { get; set; }
}

public class Virtual21
{
    public int activation { get; set; }
}

public class Virtual23
{
    public int activation { get; set; }
}

public class Virtual29
{
    public int activation { get; set; }
}

public class Virtual30
{
    public int activation { get; set; }
}

public class Virtual31
{
    public int activation { get; set; }
}

public class Virtual32
{
    public int activation { get; set; }
}

public class Virtual4
{
    public int activation { get; set; }
}

Printing to console as follows:

foreach (var field in response)
      {
        Console.WriteLine($"Name: {(field.Key)} \nCode: {field.Value.iso}");


    }

Produces:

Name: afghanistan
Code: Iso
Name: albania
Code: Iso
Name: algeria
Code: Iso

vs Expected output:

Name: afghanistan
Code: af
Name: albania
Code: al
...

this is the closest post on SO i could manage to pull from google

Upvotes: 3

Views: 261

Answers (4)

Serge
Serge

Reputation: 43880

you can accomplish your task in one line

var countries = JObject.Parse(json).Properties()
                                       .Select(jo => new
                                       {
                                         name = jo.Name,
                                         code = ((JObject)jo.Value["iso"]).Properties().First().Name
                                       }).ToList();

Upvotes: 2

NineBerry
NineBerry

Reputation: 28499

Solution without using predefined types but navigating the object structure.

string input = @"
    {
       ""afghanistan"":{
          ""iso"":{
             ""af"":1
          },
          ""prefix"":{
             ""+93"":1
          },
          ""text_en"":""Afghanistan"",
          ""text_ru"":""Афганистан"",
          ""virtual21"":{
             ""activation"":1
          },
          ""virtual23"":{
             ""activation"":1
          },
          ""virtual29"":{
             ""activation"":1
          },
          ""virtual30"":{
             ""activation"":1
          },
          ""virtual31"":{
             ""activation"":1
          },
          ""virtual32"":{
             ""activation"":1
          },
          ""virtual4"":{
             ""activation"":1
          }
       },
      ""albania"":{
          ""iso"":{
             ""al"":1
          },
          ""prefix"":{
             ""+98"":1
          },
          ""text_en"":""Albania"",
          ""virtual21"":{
             ""activation"":1
          },
          ""virtual23"":{
             ""activation"":1
          },
          ""virtual29"":{
             ""activation"":1
          },
          ""virtual30"":{
             ""activation"":1
          },
          ""virtual31"":{
             ""activation"":1
          },
          ""virtual32"":{
             ""activation"":1
          },
          ""virtual4"":{
             ""activation"":1
          }
       }
    }
";

JObject o = JObject.Parse(input);

// Foreach property of the root
foreach(JProperty country in o.Children())
{
    // Country name is name of the property
    string countryName = country.Name;

    // Get ISO Node below the country node
    JToken? iso = country.SelectToken("$..iso");

    // Get First property within iso Node
    JProperty? code = iso?.Value<JObject>()?.Properties().First();

    // Get Name of property
    string isoCode = code?.Name ?? "";

    Console.WriteLine($"Name: {countryName} \nCode: {isoCode}");
}

Upvotes: 1

Guru Stron
Guru Stron

Reputation: 142123

You can use Dictionary<string, int> to represent the Iso too (as you do with the root):

public class ResponseFields
{
    // ...
    public Dictionary<string, int> Iso { get; set; }
    // ...
}

And output line can be:

Console.WriteLine($"Name: {(field.Key)} \nCode: {field.Value.Iso.Keys.FirstOrDefault()}");

Upvotes: 1

Babak Naffas
Babak Naffas

Reputation: 12561

Is iso guaranteed to only have the single property? If so, you can just read the children by index instead of name.

Upvotes: 1

Related Questions