Is there any way to JSON.NET-serialize a subclass of List<T> that also has extra properties?
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
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; }
}
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);
}
}
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();
}
...
}
精彩评论