11

Ok, we're using Newtonsoft's JSON.NET product, which I really love. However, I have a simple class structure for hierarchical locations that look roughly like this...

public class Location
{
    public string Name{ get; set; }
    public LocationList Locations{ get; set; }
}

// Note: LocationList is simply a subclass of a List<T>
// which then adds an IsExpanded property for use by the UI.
public class LocationList : List<Location>
{
    public bool IsExpanded{ get; set; }
}

public class RootViewModel
{
    public LocationList RootLocations{ get; set; }
}

...and when I serialize them to JSON, it all works great, except the IsExpanded property on the LocationList class is excluded. Only the list's contents are serialized.

Now here's what I'm envisioning would be a good format. It's esentially the same thing as if LocationList wasn't a subclass of List<Location> but rather was just a regular object that had a property called Items of type List<Location> instead.

{
  "Locations":
  {
    "IsExpanded": true,
    "Items": [
      {
        "Name": "Main Residence",
        "Locations":
        {
          "IsExpanded": true,
          "Items": [
            {
              "Name": "First Floor",
              "Locations":
              {
                "IsExpanded": false,
                "Items": [
                  {
                    "Name": "Livingroom"
                  },
                  {
                    "Name": "Dining Room"
                  },
                  {
                    "Name": "Kitchen"
                  }
                ]
            },
            {
              "Name": "Second Floor",
              "Locations":
              {
                "IsExpanded": false,
                "Items": [
                  {
                    "Name": "Master Bedroom"
                  },
                  {
                    "Name": "Guest Bedroom"
                  }
                ]
            },
            {
              "Name": "Basement"
            }
          ]
        }
      }
    ]
  }
}

Now I also understand that Newtonsoft's product is extensible because they specifically talk about how you can write your own custom serializer for specific data types, which would be exactly what I'd want here. However, they don't have any good code examples on how to do this.

If we (the SO community) can figure this out, technically by using the above format we should be able to serialize ANY subclass of List (or its derivatives/similar objects) provided they don't already have a property called Items (which IMHO would be a poor design in the first place since it would be confusing as crap!) Perhaps we can even get Newtonsoft to roll such a thing in their serializer natively!

So that said... anyone know how to customize the serializer/deserializer to treat this object differently?

M

Mark A. Donohoe
  • 23,825
  • 17
  • 116
  • 235
  • 1
    You serialize it to json with what? json.net, datacontracts...? – Stanislav Ageev May 02 '11 at 23:22
  • Does it make a difference if you use the automatic property notation for the `IsExpanded` property? In other words, `public bool IsExpanded { get; set; }`? Not sure if that has anything to do with it, but it's the thing that's standing out to me as being different about that property. – ataddeini May 02 '11 at 23:24
  • 1
    @ataddeini I can't see that as being the issue since the compiled IL would be identical either way. The only difference would be the name of the private field. I think it more likely that whatever serializer being used optimizes for List, and is not properly picking up that LocationList is a derived type. – Harry Steinhilber May 03 '11 at 00:02
  • @Harry Steinhilber: Yes, I agree. Good point. – ataddeini May 03 '11 at 00:34
  • @ataddeini, I changed it back. Was bugging me why I had it that way here, but then I remembered in the code I copied, we were using the member variable but I had stripped that part out for clarity. I just forgot to change it to the shorter version here, which as I said, now I have. Thanks! :) – Mark A. Donohoe May 04 '11 at 02:03
  • @Stanislav Ageev, we're using Newtonsoft's JSON.NET product. Love it (except for this issue!) – Mark A. Donohoe May 04 '11 at 02:07
  • This behavior makes no sense, and now it's doing the opposite, serializing the properties of the list subclass, but none of its items. – Triynko Mar 09 '16 at 03:51
  • [Here](http://stackoverflow.com/questions/14383736/how-to-serialize-deserialize-a-custom-collection-with-additional-properties-usin) is a similar question with an alternative solution. – Little Endian Jun 15 '16 at 17:57

3 Answers3

2

Usually when I find myself fighting something like this it tells me I should consider another approach. In this case, I would recommend the following view model structure as an alternative:

public class Location
{
    public bool IsExpanded { get; set; }
    public string Name { get; set; }
    public List<Location> Locations { get; set; }
}

public class ViewModel
{
    public List<Location> RootLocations { get; set; }
}
ataddeini
  • 4,851
  • 23
  • 34
  • 5
    I Thought about that but IMHO that way is bad for two reasons. One, that separates the property from what it applies to, namely the list (and thus you have to duplicate it again on the root ViewModel object as well as anywhere else you want such a list. This also muddies up your bindings and such since it is a different object. But more importantly, two, you should not have to change the (view)model because of a limitation in the serializer! That's backwards! You find a newer storage mechanism if the one you have doesn't fulfill your needs. – Mark A. Donohoe May 03 '11 at 04:40
  • @MarqueIV: Reasonable tradeoffs in my opinion. Better to address the issue and move on instead of dwelling on it and taking away from other tasks. – ataddeini May 03 '11 at 12:06
  • touche! Well half-touche as you've now added the aforementioned binding and such issues that weren't there before. So purist: I win. Making money instead of gloating you're a purist: You do. (...and only one of us likely has 'Always meets deadlines' on our resumes! LOL!) – Mark A. Donohoe May 03 '11 at 21:43
  • You should do it this way. How is someone going to deserialize you correctly if someone else gets a hold of whats serialized. Name the child list "Items" and your naming problem goes away. – BradLaney Sep 25 '12 at 16:29
  • And when you don't own the type that you want to serialize? It kind of bites (and is surprising) that JSON.NET falls flat in this area. – A.R. May 13 '14 at 13:09
  • @Brad, not sure what you mean about a 'naming problem'. The issue is a property that's being ignored by the serializer because it sees it's of a list type so it doesn't look at anything else except its children. Very frustrating. – Mark A. Donohoe Aug 04 '15 at 21:50
  • I haven't looked at this in forever. But if I recall correctly, most frameworks will automatically support it if the child list is named "Items". I think JSON.NET does this, and so does a few others I checked at the time. This is 3 year old speculation. – BradLaney Sep 02 '15 at 18:29
  • I'm seeing the exact opposite problem with JSON.NET. My subclass of LIst is having all it's properties serialized correctly, except the list items. It's not serializing any of the list items at all. It seems to be treating it as an object and completely ignoring the fact that it's a list. There must be some special logic for handling generic lists. – Triynko Mar 09 '16 at 03:50
1

Ok... so here's what I've come up with. I had to write my own JsonConverter. I basically use it to create an inline JObject that has the properties structured as I wanted them to persist, then I persist that. I then do the reverse when I read it back out.

However, the down'side is it doesn't use reflection or any other such things so this only works for this specific type which I had to hand-code property by property (in this case there are only two so that's good!) and it also doesn't take advantage of the DefaultValues processing which I have to re-emulate manually, meaning the attributes are basically ignored unless I reflect upon them. Still, this works. Perfect? No, but hey... things rarely are!

Of course, comments are welcome and encouraged!

public class LocationListJsonConverter : JsonConverter
{
    public override bool CanConvert(System.Type objectType)
    {
        return objectType == typeof(LocationList);
    }

    public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer)
    {
        var locationList = (existingValue as LocationList) ?? new LocationList();
        var jLocationList = JObject.ReadFrom(reader);

        locationList.IsExpanded = (bool)(jLocationList["IsExpanded"] ?? false);

        var jLocations = jLocationList["_Items"];
        if(jLocations != null)
        {
            foreach(var jLocation in jLocations)
            {
                var location = serializer.Deserialize<Location>(new JTokenReader(jLocation));
                locationList.Add(location);
            }
        }

        return locationList;

    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var locationList = value as LocationList;

        JObject jLocationList = new JObject();

        if(locationList.IsExpanded)
            jLocationList.Add("IsExpanded", true);

        if(locationList.Count > 0)
        {
            var jLocations = new JArray();

            foreach(var location in locationList)
            {
                jLocations.Add(JObject.FromObject(location, serializer));
            }

            jLocationList.Add("_Items", jLocations);

        }

        jLocationList.WriteTo(writer);

    }

}
Mark A. Donohoe
  • 23,825
  • 17
  • 116
  • 235
0

I need a class named FieldGroup that also has some properties to group some Fields. I did that as this firstly.

public class FieldGroup : List<Field>{ ... }

It has the problem to serialize as the post said. So I modified the class as below. So I can handle with it the same as the class of *FieldGroup that derived from List<Field>.

public class FieldGroup : IPrintable, IEnumerable<Field>
{
    public PrintFormat GroupFormat { get; set; } = new PrintFormat();
    public List<Field> Fields { get; set; } = new List<Field>();

    public Field this[int index]
    {
        get => Fields[index];
        set => Fields[index] = value;
    }

    public void Add(Field field)
    {
        Fields.Add(field);
    }
    public IEnumerator<Field> GetEnumerator()
    {
        return new FieldEnumerator(Fields);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    ...
}
  • Hi, and welcome to Stack!:) Question for you... what is `FieldEnumerator` here? Was that a class you created to enumerate your fields? If so, couldn't you just have done this... `return Fields.GetEnumerator();` because `Fields` is a `List` which can already be enumerated, so why not just delegate to it? Also, technically this doesn't answer my question which is adding properties to an existing collection. This wraps an existing collection, but then lets you enumerate over it as if it were that collection. Clever, but not quite what I was asking. Still, it's good info for others. – Mark A. Donohoe Sep 18 '20 at 04:09
  • @MarkA.Donohoe:Yes! You are pretty right. Thank you for your advise! The way is just a trick for me to avoid changing code elsewhere. Hope to offer little help for others:). – LIWUQINGXIN Sep 22 '20 at 12:21