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
Json.NET has excellent support for serializing and deserializing collections of objects. To serialize a collection - a generic list, array, dictionary, or your own custom collection - simply call the serializer with the object you want to get JSON for.
The exception thrown when an error occurs during JSON serialization or deserialization.
There is no polymorphic deserialization (equivalent to Newtonsoft. Json's TypeNameHandling ) support built-in to System. Text. Json .
All fields, both public and private, are serialized and properties are ignored.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With