I need to convert the following JArray into an Object.
My problem is, that i need to do this with Attributes.
Because i'm not able to change the following call in a decoupled class:
var message = JsonConvert.DeserializeObject(e.Message, messageType)
The JSON
[71,[[77372266,1508067366000,-0.12144759,5517.4],[77372265,1508067366000,-0.020001,5518.2],[77372251,1508067362000,-0.01,5517.2],[77372235,1508067358000,0.020001,5517.8],[77372220,1508067352000,-0.41883579,5517.3],[77372218,1508067352000,-1.05740887,5517.2],[77372211,1508067351000,-1.8084,5517.2],[77372207,1508067350000,-0.01565918,5517.2],[77372206,1508067349000,-0.01721768,5514.3],[77372205,1508067349000,-0.10347625,5514.3],[77372201,1508067348000,-1.08183286,5513.1],[77372200,1508067348000,-0.04218858,5513.1],[77372199,1508067348000,-0.020001,5513.3],[77372198,1508067348000,-0.020001,5513.3],[77372197,1508067348000,-0.020001,5513.3],[77372196,1508067348000,-0.020001,5513.3],[77372195,1508067348000,-2.16396207,5513.3],[77372194,1508067348000,-1.5601,5513.5],[77372193,1508067348000,-0.13520397,5514.2],[77372192,1508067347000,-0.01479603,5514.2],[77372191,1508067347000,-0.13659854,5513.4],[77372189,1508067346000,-0.01597635,5513.3],[77372187,1508067346000,-0.19812221,5513.3],[77372184,1508067346000,-0.01261482,5513.3],[77372183,1508067346000,0.131033,5519.7],[77372182,1508067346000,0.2011,5519.4],[77372181,1508067346000,0.074337,5518.2],[77372180,1508067346000,0.125,5517.5],[77372179,1508067346000,0.06401,5516.5],[77372178,1508067346000,0.05305,5516.3]]]
My Classes
public class Root
{
public int ChannelId { get; set; }
public List<Item> Data { get; set; }
}
public class Item
{
public int Id { get; set; }
public long Timestamp { get; set; }
public decimal Price { get; set; }
public decimal Size { get; set; }
}
If you use the Newtonsoft JSON library (nowadays referred to as JSON.NET) you can use the Jsonconverter class to create your own custom converter.
Your class would then look something like:
public class MyCustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Root).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
JArray rootToken = JArray.Load(reader);
if (rootToken[0] != null && rootToken[1] != null)
{
var root = new Root(rootToken[0].Value<int>(), this.readItems(rootToken[1]));
return root;
}
}
return existingValue;
}
private IList<Item> readItems(JToken items)
{
var itemList = new List<Item>();
if (items.Type == JTokenType.Array)
{
foreach(var item in items.Children())
{
if (item.Type == JTokenType.Array && item.Count() == 4)
{
itemList.Add(new Item(
item[0].Value<int>(),
item[1].Value<long>(),
item[2].Value<decimal>(),
item[3].Value<decimal>()));
}
}
}
return itemList;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Note that this custom converter in the current state is only capable of transforming JSON to an object, if you want it to be able to transform a Root object to a JSON string you need to implement the WriteJson method yourself.
You said that because of a decoupled dependency you cannot change the way the callee calls the deserialize method. What you can do is overwrite the JSONConvert.DefaultSettings property and tell it explicitly to use the custom converter.
JsonConvert.DefaultSettings = (() =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new MyCustomConverter());
return settings;
});
var jsonString = "...";
var root = JsonConvert.DeserializeObject(jsonString, typeof(Root));
First parse your json to JArray and then you can simply create a function to parse it into an object of your choice. You can try this:
string json = "[71,[[77372266,1508067366000,-0.12144759,5517.4],[77372265,1508067366000,-0.020001,5518.2],[77372251,1508067362000,-0.01,5517.2],[77372235,1508067358000,0.020001,5517.8],[77372220,1508067352000,-0.41883579,5517.3],[77372218,1508067352000,-1.05740887,5517.2],[77372211,1508067351000,-1.8084,5517.2],[77372207,1508067350000,-0.01565918,5517.2],[77372206,1508067349000,-0.01721768,5514.3],[77372205,1508067349000,-0.10347625,5514.3],[77372201,1508067348000,-1.08183286,5513.1],[77372200,1508067348000,-0.04218858,5513.1],[77372199,1508067348000,-0.020001,5513.3],[77372198,1508067348000,-0.020001,5513.3],[77372197,1508067348000,-0.020001,5513.3],[77372196,1508067348000,-0.020001,5513.3],[77372195,1508067348000,-2.16396207,5513.3],[77372194,1508067348000,-1.5601,5513.5],[77372193,1508067348000,-0.13520397,5514.2],[77372192,1508067347000,-0.01479603,5514.2],[77372191,1508067347000,-0.13659854,5513.4],[77372189,1508067346000,-0.01597635,5513.3],[77372187,1508067346000,-0.19812221,5513.3],[77372184,1508067346000,-0.01261482,5513.3],[77372183,1508067346000,0.131033,5519.7],[77372182,1508067346000,0.2011,5519.4],[77372181,1508067346000,0.074337,5518.2],[77372180,1508067346000,0.125,5517.5],[77372179,1508067346000,0.06401,5516.5],[77372178,1508067346000,0.05305,5516.3]]]";
var array = JArray.Parse(json);
var channelId = array[0];
var listData = array[1].ToList();
Root root = new Root();
root.ChannelId = Convert.ToInt32(channelId);
root.Data = new List<Item>();
for (int i = 0; i < listData.Count; i++)
{
Item newItem = new Item();
var item = listData[i].ToList();
newItem.Id = Convert.ToInt32(item[0]);
newItem.Timestamp = Convert.ToInt64(item[1]);
newItem.Price = Convert.ToDecimal(item[2]);
newItem.Size = Convert.ToDecimal(item[3]);
root.Data.Add(newItem);
}
Related
I have two classes
// contains names/keys for JSON serialization
public class Mapping
{
public string Id { get; set; }
public string Name { get; set; }
}
// contains values for JSON serialization
public class Data
{
public string Id { get; set; }
public string Name { get; set; }
}
var map = new Mapping { Id = "Code", Name = "Tiltle" };
var data = new Data { Id = "Test1234", Name = "testname1234" };
so when data is serialized using the mapping from map, JSON should look like this
{
"Code":"Test1234"
"Tiltle":"testname1234"
}
You need a custom JsonConverter and because your map object is created at runtime (I suppose), the converter has to be created at runtime as well.
Try:
var settings = new JsonSerializerSettings();
settings.Converters.Add(new PropertyNameFromMapTypeJsonConverter(map, typeof(Test2)));
var json = JsonConvert.SerializeObject(Data, settings);
The converter:
class PropertyNameFromMapTypeJsonConverter : JsonConverter
{
private readonly IDictionary<string, object> _mappings;
private readonly Type _targetType;
public PropertyNameFromMapTypeJsonConverter(object mapObj, Type targetType)
{
// mapobj is instance of Test1
// Use reflection to create a dictionary used as mappings between Test1 and 2
_mappings = TypeDescriptor.GetProperties(mapObj)
.OfType<PropertyDescriptor>()
.ToDictionary(prop => prop.Name, prop => prop.GetValue(mapObj));
_targetType = targetType;
}
public override bool CanConvert(Type objectType)
{
return objectType == _targetType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Do not support deserialize
throw new NotSupportedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dict = TypeDescriptor.GetProperties(value)
.OfType<PropertyDescriptor>()
.ToDictionary(prop => _mappings[prop.Name], prop => prop.GetValue(value));
serializer.Serialize(writer, dict);
}
}
I have this class:
public class ValueInteger
{
[JsonIgnore]
public string ValueName { get; set; }
public int Value { get; set; }
[JsonProperty("timestamp")]
public UInt64 TimeStamp { get; set; }
}
Given an instance of:
var valueInt = new ValueInteger
{
ValueName = "mycounter",
Value = 7,
TimeStamp = 1010101010
}
It should serialize to:
{ mycounter: 7, timestamp = 1010101010 }
It would be cool if one could declare the Value property as
[JsonRedirect(titlePropertyName: nameof(ValueName))]
public int Value { get; set; }
I probably have to implement my own ContractResolver, and have studiet this post: https://stackoverflow.com/a/47872645/304820
but it depends on the IValueProvider, and AFAIK there is no INameProvider to use for renaming.
Usually renaming is done per class, not per instance.
My approach to this would be to write my own Converter. The converter simply serializes in the same fashion as a normal converter, but whenever it comes across a special attribute on a property, it should rename that property in the output.
So, serializing a C# object would go like this:
First convert the C# object into a JSON object (i.e. JTokens structure).
Run through the properties in the C# object and find the ones that need to be renamed...
For each of those properties, determine what their current name is and what their new name should be.
Perform the renaming on the JSON object.
Finally serialize the JSON object into a string.
I have made a simple implementation of this. The usage looks like this:
class Program
{
static void Main(string[] args)
{
var valueInt = new ValueInteger
{
ValueName = "mycounter",
Value = 7,
TimeStamp = 1010101010
};
var settings = new JsonSerializerSettings { Converters = new JsonConverter[] { new DynamicNameConverter() } };
var result = JsonConvert.SerializeObject(valueInt, settings);
Console.WriteLine(result);
Console.Read();
}
}
public class ValueInteger
{
[JsonIgnore]
public string ValueName { get; set; }
[JsonDynamicName(nameof(ValueName))]
public int Value { get; set; }
[JsonProperty("timestamp")]
public UInt64 TimeStamp { get; set; }
}
And the helper classes:
class DynamicNameConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// Only use this converter for classes that contain properties with an JsonDynamicNameAttribute.
return objectType.IsClass && objectType.GetProperties().Any(prop => prop.CustomAttributes.Any(attr => attr.AttributeType == typeof(JsonDynamicNameAttribute)));
}
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// We do not support deserialization.
throw new NotImplementedException();
}
public override bool CanWrite => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var token = JToken.FromObject(value);
if (token.Type != JTokenType.Object)
{
// We should never reach this point because CanConvert() only allows objects with JsonPropertyDynamicNameAttribute to pass through.
throw new Exception("Token to be serialized was unexpectedly not an object.");
}
JObject o = (JObject)token;
var propertiesWithDynamicNameAttribute = value.GetType().GetProperties().Where(
prop => prop.CustomAttributes.Any(attr => attr.AttributeType == typeof(JsonDynamicNameAttribute))
);
foreach (var property in propertiesWithDynamicNameAttribute)
{
var dynamicAttributeData = property.CustomAttributes.FirstOrDefault(attr => attr.AttributeType == typeof(JsonDynamicNameAttribute));
// Determine what we should rename the property from and to.
var currentName = property.Name;
var propertyNameContainingNewName = (string)dynamicAttributeData.ConstructorArguments[0].Value;
var newName = (string)value.GetType().GetProperty(propertyNameContainingNewName).GetValue(value);
// Perform the renaming in the JSON object.
var currentJsonPropertyValue = o[currentName];
var newJsonProperty = new JProperty(newName, currentJsonPropertyValue);
currentJsonPropertyValue.Parent.Replace(newJsonProperty);
}
token.WriteTo(writer);
}
}
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
class JsonDynamicNameAttribute : Attribute
{
public string ObjectPropertyName { get; }
public JsonDynamicNameAttribute(string objectPropertyName)
{
ObjectPropertyName = objectPropertyName;
}
}
Please be aware that a lot of error handling could be put into DynamicNameConverter but I have left it out to make it easier to read and understand.
I create a class for define my request, I don't get the accepted JSON string
I define this object:
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
when I convert it to json, I get the following string:
{"requests":[{"var_args":[{"title":"Test","owner":"skaner"}]}]}
how can I define the class, for get the accepted json string:
{"requests":[{"var_args":[{"title":"Test"},{"owner":"skaner"}]}]}
You can write a custom JSON converter that can serialize every property of an object (of a known type) into a different JSON object.
public class PropertyAsObjectConverter : JsonConverter
{
private readonly Type[] _types;
public PropertyAsObjectConverter(params Type[] types)
{
_types = types;
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var name = property.Name;
var attrs = property.GetCustomAttributes(typeof(JsonPropertyAttribute));
if(attrs != null)
{
if (attrs.FirstOrDefault() is JsonPropertyAttribute attr)
name = attr.PropertyName;
}
writer.WriteStartObject();
writer.WritePropertyName(name);
serializer.Serialize(writer, property.GetValue(value));
writer.WriteEndObject();
}
}
}
This implements only the serialization, but you can extend it to support deserialization too. You can also extend it to serialize fields should you need that.
You can then define your classes as follows. Notice that I am using JsonPropertyAttribute here to specify the name in the serialized JSON.
public class Content
{
[JsonProperty("requests")]
public Request Value { get; set; }
}
public class Request
{
[JsonProperty("var_args")]
public VarArgs[] Arguments { get; set; }
}
public class VarArgs
{
[JsonProperty("title")]
public object Title { get; set; }
[JsonProperty("owner")]
public object Owner { get; set; }
}
This is how you can use it:
static void Main(string[] args)
{
var request = new Content()
{
Value = new Request()
{
Arguments = new VarArgs[]
{
new VarArgs()
{
Title = "Test",
Owner = "Skaner",
}
}
}
};
var text = JsonConvert.SerializeObject(
request,
Formatting.None,
new PropertyAsObjectConverter(typeof(VarArgs)));
Console.WriteLine(text);
}
The output for this sample is the one you expect:
{"requests":{"var_args":[{"title":"Test"},{"owner":"Skaner"}]}}
You could use a custom JsonConverter like the below.
It takes the Var_Args object and splits it in two different JObject which correspond to two different JSON objects.
public class VarArgsConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = (JObject)JToken.FromObject(value);
var objTitle = new JObject();
objTitle.Add("title", obj.GetValue("title"));
var objOwner = new JObject();
objOwner.Add("owner", obj.GetValue("owner"));
objTitle.WriteTo(writer);
objOwner.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Var_Args);
}
}
public class Wrapper
{
[JsonProperty("requests")]
public Request Requests { get; set; }
}
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
Then use it:
var wrapper = new Wrapper();
var request = new Request();
request.var_args = new Var_Args[] {
new Var_Args(){ title = "Test", owner = "skaner" },
new Var_Args(){ title = "Test2", owner = "skaner2" }
};
wrapper.Requests = request;
var serialized = JsonConvert.SerializeObject(wrapper, new VarArgsConverter());
Output
{"requests":{"var_args":[{"title":"Test"},{"owner":"skaner"},{"title":"Test2"},{"owner":"skaner2"}]}}
Note: I'm using the Wrapper class just to produce the requested JSON.
If you don't want to specify the converter each time, you can register your converter globally. Please see this answer which explains how you can do that. So, the serializer will use your custom JsonConverter every time you try to serialize a Var_Args object.
If you register the JsonConvert globally you can use:
var serialized = JsonConvert.SerializeObject(wrapper);
You can use System.Reflection to redefine Var_Args as an implementation of the IEnumerable<Dictionary<string,object>> interface by adding two methods to the class:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
var Properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Entry = new Dictionary<string,object>();
Entry.Add(Property.Name, Property.GetValue(this));
yield return Entry;
}
}
public object title { get; set; }
public object owner { get; set; }
}
While Reflection may be regarded as slow, there is a technique you can use to statically compile an IEnumerable at runtime so that the reflection only occurs once for the definition of the class, like this:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
private struct PropertyList<T>
{
public static readonly List<Func<T,Dictionary<string,object>>> PropertyGetters;
static PropertyList()
{
PropertyGetters = new List<Func<T,Dictionary<string,object>>>();
var Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Args = new [] { Expression.Parameter(typeof(T)) };
var Key = Property.Name;
var Value = Expression.Property(Args[0], Property);
Func<T,object> Get = Expression.Lambda<Func<T,object>>(Value, Args).Compile();
PropertyGetters.Add(obj =>
{
var entry = new Dictionary<string,object>();
entry.Add(Key, Get(obj));
return entry;
});
}
}
}
protected static IEnumerable<Dictionary<string,object>> GetPropertiesAsEntries<T>(T obj)
{
return PropertyList<T>.PropertyGetters.Select(f => f(obj));
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
return GetPropertiesAsEntries(this).GetEnumerator();
}
public object title { get; set; }
public object owner { get; set; }
}
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; }
}
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'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
Here is similar solution you may apply. See Serialize json to an object with catch all dictionary property The answer by David Hoerster.
I have a class that extends the basic ObservableCollection<T> class (adds some more properties). When I serialize the class using json.net, it omits the added properties. For example, the following class:
public class ObservableCollectionExt : ObservableCollection<int>
{
[DataMember]
public string MyData1 { get; set; }
[DataMember]
public string MyData2 { get; set; }
public ObservableCollectionExt()
{
}
[JsonConstructor]
public ObservableCollectionExt(string mydata1, string mydata2)
{
MyData1 = mydata1;
MyData2 = mydata2;
}
public static ObservableCollectionExt Create()
{
ObservableCollectionExt coll = new ObservableCollectionExt("MyData1", "MyData2");
coll.Add(1);
coll.Add(2);
coll.Add(3);
return coll;
}
}
gets serialized as follows (with values for MyData1 and MyData2 missing):
{
"$type": "Test1.ObservableCollectionExt, Test1",
"$values": [
1,
2,
3
]
}
How can I include the extra properties in the serialized data?
You may need a custom converter. Not sure that this is the best way to do it, but it seems to work.
public class MyCustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ObservableCollectionExt);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ObservableCollectionExt result = new ObservableCollectionExt();
string type = null;
int i;
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
type = reader.Value.ToString();
else if (reader.TokenType == JsonToken.EndObject)
return result;
else if (!string.IsNullOrEmpty(type) && reader.Value != null)
{
switch (type)
{
case "mydata1":
{
result.MyData1 = reader.Value.ToString();
break;
}
case "mydata2":
{
result.MyData2 = reader.Value.ToString();
break;
}
case "elements":
{
if (int.TryParse(reader.Value.ToString(), out i))
result.Add(i);
break;
}
}
}
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ObservableCollectionExt o = (ObservableCollectionExt)value;
writer.WriteStartObject();
writer.WritePropertyName("mydata1");
writer.WriteValue(o.MyData1);
writer.WritePropertyName("mydata2");
writer.WriteValue(o.MyData2);
writer.WritePropertyName("elements");
writer.WriteStartArray();
foreach (var val in o)
writer.WriteValue(val);
writer.WriteEndArray();
writer.WriteEndObject();
}
}
This produces strings like this: {\"mydata1\":\"MyData1\",\"mydata2\":\"MyData2\",\"elements\":[1,2,3]}
Use the converter like this:
ObservableCollectionExt o = ObservableCollectionExt.Create();
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new MyCustomConverter());
string serialized = JsonConvert.SerializeObject(o, settings);
ObservableCollectionExt deserialized = JsonConvert.DeserializeObject<ObservableCollectionExt>(serialized, settings);
EDIT:
I realize the converter would only work for simple cases when the custom properties are not complex types. There is another way, a workaround with an anonymous type:
ObservableCollectionExt o = ObservableCollectionExt.Create();
string serialized = JsonConvert.SerializeObject(new { MyData1 = o.MyData1, MyData2 = o.MyData2, coll = o });
var anonType = new { MyData1 = null as object, MyData2 = null as object, coll = null as object };
dynamic d = JsonConvert.DeserializeAnonymousType(serialized, anonType);
ObservableCollectionExt deserialized = new ObservableCollectionExt(d.MyData1, d.MyData2);
foreach (var elem in d.coll)
deserialized.Add((int)elem);