3

Say I have some Json that will come in a packet like this:

{
    "LinkType1": "google",
    "LinkUrl1": "https://plus.google.com/test",
    "LinkShow1": 1,

    "LinkType2": "facebook",
    "LinkUrl2": "https://www.facebook.com/test",
    "LinkShow2": 0,

    "LinkType3": "linkedin",
    "LinkUrl3": "http://www.linkedin.com/test",
    "LinkShow3": 1,

    "count": 3,
    "errorCode": 0,
    "errorMessage": "Success"
}

Notice how everything comes back as the same property, but with an index on it?

I would love to be able to deserialize that data as though it was an array instead of single properties. What would be the best method for deserializing this into the classes below? I'm using the Newtonsoft Json library for serialization, so a solution using that would be preferred.

    public class LinksResult
    {
        public List<LinkData> Links { get; set; } 

        [JsonProperty("count")]
        public int Count { get; set; }

        [JsonProperty("errorCode")]
        public int ErrorCode { get; set; }

        [JsonProperty("errorMessage")]
        public string ErrorMessage { get; set; }
    }

    public class LinkData
    {
        public string LinkType { get; set; }
        public string LinkUrl { get; set; }
        public bool LinkShow { get; set; }
    }
Zachary Dow
  • 1,797
  • 18
  • 35

3 Answers3

2

You can use a custom JsonConverter to deserialize the JSON data into the structure that you want. Here is what the code for the converter might look like.

class LinksResultConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(LinksResult));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);
        LinksResult result = new LinksResult();
        result.Count = (int)obj["count"];
        result.ErrorCode = (int)obj["errorCode"];
        result.ErrorMessage = (string)obj["errorMessage"];
        result.Links = new List<LinkData>();

        for (int i = 1; i <= result.Count; i++)
        {
            string index = i.ToString();
            LinkData link = new LinkData();
            link.LinkType = (string)obj["LinkType" + index];
            link.LinkUrl = (string)obj["LinkUrl" + index];
            link.LinkShow = (int)obj["LinkShow" + index] == 1;
            result.Links.Add(link);
        }

        return result;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

To use the converter, just add a [JsonConverter] attribute to your LinksResult class as shown below. (Note that you don't need the [JsonProperty] attributes with this approach, since the mapping between JSON property names and the actual class members is handled directly by the converter.)

[JsonConverter(typeof(LinksResultConverter))]
public class LinksResult
{
    public List<LinkData> Links { get; set; }
    public int Count { get; set; }
    public int ErrorCode { get; set; }
    public string ErrorMessage { get; set; }
}

Then, you can deserialize like this:

LinksResult result = JsonConvert.DeserializeObject<LinksResult>(json);

Fiddle: https://dotnetfiddle.net/56b34H

Brian Rogers
  • 118,414
  • 30
  • 277
  • 278
1

Brian's answer was very good and it got me 80% of the way to where I wanted to be. However it's not a very good implementation to use over and over again if this sort of pattern happens on many different objects.

I made something more generic. An interface that a "Page" would have.

public interface IPage<TItem>
{
    int Count { get; set; }
    List<TItem> PageItems { get; set; }
}

Then the Page converter itself.

public class PageConverter<TPage, TItem> : JsonConverter
        where TPage : IPage<TItem>, new()
        where TItem : new()
{
    private readonly Regex _numberPostfixRegex = new Regex(@"\d+$");

    public override bool CanWrite
    {
        get { return false; }
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(TPage));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = serializer.Deserialize<JObject>(reader);
        var page = new TPage();
        serializer.Populate(obj.CreateReader(), page); //Loads everything that isn't a part of the items. 
        page.PageItems = new List<TItem>();

        for (int i = 1; i <= page.Count; i++)
        {
            string index = i.ToString();

            //Find all properties that have a number at the end, then any of those that are the same number as the current index.
            //Put those in a new JObject.
            var jsonItem = new JObject();
            foreach (var prop in obj.Properties().Where(p => _numberPostfixRegex.Match(p.Name).Value == index))
            {
                jsonItem[_numberPostfixRegex.Replace(prop.Name, "")] = prop.Value;
            }

            //Deserialize and add to the list.
            TItem item = jsonItem.ToObject<TItem>(serializer);
            page.PageItems.Add(item);
        }

        return page;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

So then all that's needed is to implement it on the links result:

[JsonConverter(typeof(PageConverter<LinksResult, LinkData>))]
public class LinksResult : IPage<LinkData>
{
    public int Count { get; set; }

    public List<LinkData> PageItems { get; set; }
}

I figured out you can control the serialization of capitalization with JsonSerializerSettings, so best leave that detail up to the chosen serializer, not my converter.

Fiddle here: https://dotnetfiddle.net/7KhwYY

Community
  • 1
  • 1
Zachary Dow
  • 1,797
  • 18
  • 35
0

Here is similar solution you may apply. See Serialize json to an object with catch all dictionary property The answer by David Hoerster.

Community
  • 1
  • 1
Dmytro
  • 240
  • 3
  • 9