简体   繁体   中英

How do I deserialize an array of objects with varying names?

In a Windows Phone app using C#, I am trying to deserialize some JSON with the following structure:

[ { "kite" : { "supplier" : "ABC",
        "currency" : "GBP",
        "cost" : "7.98"
      } },
  { "puzzle" : { "supplier" : "DEF",
        "currency" : "USD",
        "cost" : "7.98"
      } },
  { "ball" : { "supplier" : "DEF",
        "currency" : "USD",
        "cost" : "5.49"
      } }
]

This is a list of toys in which the names of the toys (kite, puzzle, ball) are not known in advance. I don't have any control over the format of the JSON.

Using json2csharp.com I get the following classes:

public class Kite
{
    public string supplier { get; set; }
    public string currency { get; set; }
    public string cost { get; set; }
}

public class Puzzle
...

public class Ball
...

public class RootObject
{
    public Kite kite { get; set; }
    public Puzzle puzzle { get; set; }
    public Ball ball { get; set; }
}

This looks to me like an array of "toy" objects but I don't know what approach to take when deserializing this.

The only code that I have had work is the basic:

var root = JsonConvert.DeserializeObject(rawJSON);

I thought something like the following might work, but I would lose the name of the toy if it worked (and it doesn't):

public class Toy
{
    public string supplier { get; set; }
    public string currency { get; set; }
    public string cost { get; set; }
}
List<Toy> toyList = (List<Toy>) JsonConvert.DeserializeObject(rawJSON, typeof(List<Toy>));

Any suggestions please?

You are close. If you define your Toy class as you have it in your question, you can deserialize into a List<Dictionary<string, Toy>> . Thus, each toy is actually represented by a Dictionary with a single entry in it. The Key is the name of the toy and the Value is the Toy info.
Here is a demo:

string json = @"
[ { ""kite"" : { ""supplier"" : ""ABC"",
        ""currency"" : ""GBP"",
        ""cost"" : ""7.98""
      } },
  { ""puzzle"" : { ""supplier"" : ""DEF"",
        ""currency"" : ""USD"",
        ""cost"" : ""7.98""
      } },
  { ""ball"" : { ""supplier"" : ""DEF"",
        ""currency"" : ""USD"",
        ""cost"" : ""5.49""
      } }
]";

List<Dictionary<string, Toy>> list = 
       JsonConvert.DeserializeObject<List<Dictionary<string, Toy>>>(json);

foreach (Dictionary<string, Toy> dict in list)
{
    KeyValuePair<string, Toy> kvp = dict.First();
    Console.WriteLine("toy: " + kvp.Key);
    Console.WriteLine("supplier: " + kvp.Value.Supplier);
    Console.WriteLine("cost: " + kvp.Value.Cost + " (" + kvp.Value.Currency + ")");
    Console.WriteLine();
}

This outputs the following:

toy: kite
supplier: ABC
cost: 7.98 (GBP)

toy: puzzle
supplier: DEF
cost: 7.98 (USD)

toy: ball
supplier: DEF
cost: 5.49 (USD)

Admittedly, this solution is kind of "clunky", because it would be better to have the name of the toy included in the Toy class itself rather than have an intervening Dictionary to stumble over. There are two ways to fix this. One way is to add a Name property on the Toy class, deserialize into the same structure as shown above, then do a little bit of post-processing to move the names from each Dictionary into the respective Toy , building a new List<Toy> in the process. The second way to do it is to create a custom JsonConverter to handle this translation during deserialization. I'd be happy to demonstrate either of these alternate approaches if you wish. Just let me know. If you just need quick-and-dirty, then the above approach should do.

Alternative approach using a custom JsonConverter

This approach is a little "cleaner" because we can keep all the Toy info together on a single strongly-typed object, and keep all the deserialization logic separate so it doesn't clutter up the main code.

First, we need to alter your Toy class to give it a Name property.

class Toy
{
    public string Name { get; set; }
    public string Supplier { get; set; }
    public string Currency { get; set; }
    public decimal Cost { get; set; }
}

Next, we create a class which inherits from JsonConverter .

class ToyConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // This lets JSON.Net know that this converter can handle Toy objects
        return (objectType == typeof(Toy));
    }

    public override object ReadJson(JsonReader reader, 
        Type objectType, object existingValue, JsonSerializer serializer)
    {
        // load the toy JSON object into a JObject
        JObject jo = JObject.Load(reader);

        // get the first (and only) property of the object
        JProperty prop = jo.Properties().First();

        // deserialize the value of that property (which is another
        // object containing supplier and cost info) into a Toy instance
        Toy toy = prop.Value.ToObject<Toy>();

        // get the name of the property and add it to the newly minted toy
        toy.Name = prop.Name;

        return toy;
    }

    public override void WriteJson(JsonWriter writer, 
        object value, JsonSerializer serializer)
    {
        // If you need to serialize Toys back into JSON, then you'll need
        // to implement this method.  We can skip it for now.
        throw new NotImplementedException();
    }
}

To use the converter, we just need to create an instance of it and pass it in the call to DeserializeObject<T>() . Now that we have this converter, we can deserialize directly into a List<Toy> , which is much more natural.

List<Toy> toys = JsonConvert.DeserializeObject<List<Toy>>(json, new ToyConverter());

Accessing the toy data from there is straightforward.

foreach (Toy toy in toys)
{
    Console.WriteLine("toy: " + toy.Name);
    Console.WriteLine("supplier: " + toy.Supplier);
    Console.WriteLine("cost: " + toy.Cost + " (" + toy.Currency + ")");
    Console.WriteLine();
}

You'll notice this gives exactly the same output as the previous example, but the code is much cleaner.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM