Creating a generic root object for JSON.NET? [duplicate] - c#

Is there a way to change name of Data property during serialization, so I can reuse this class in my WEB Api.
For an example, if i am returning paged list of users, Data property should be serialized as "users", if i'm returning list of items, should be called "items", etc.
Is something like this possible:
public class PagedData
{
[JsonProperty(PropertyName = "Set from constructor")]??
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
EDIT:
I would like to have a control over this functionality, such as passing name to be used if possible. If my class is called UserDTO, I still want serialized property to be called Users, not UserDTOs.
Example
var usersPagedData = new PagedData("Users", params...);

You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.
First let's define some custom attributes:
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}
public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}
And then the resolver:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}
protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";
if (name.EndsWith("s"))
return name + "es";
return name + "s";
}
}
Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:
public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}
And decorate your DTO classes with the [JsonPluralName] attribute:
[JsonPluralName("Users")]
public class UserDTO
{
...
}
[JsonPluralName("Items")]
public class ItemDTO
{
...
}
Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
string json = JsonConvert.SerializeObject(pagedData, settings);
Fiddle: https://dotnetfiddle.net/GqKBnx
If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).
JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();
Another Approach
Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.
Here is the code for the converter:
public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}
JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:
[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}
And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.
Fiddle: https://dotnetfiddle.net/8E8fEE

UPD Sep 2020: #RyanHarlich pointed that proposed solution doesn't work out of the box. I found that Newtonsoft.Json doesn't initialize getter-only properties in newer versions, but I'm pretty sure it did ATM I wrote this answer in 2016 (no proofs, sorry :).
A quick-n-dirty solution is to add public setters to all properties ( example in dotnetfiddle ). I encourage you to find a better solution that keeps read-only interface for data objects. I haven't used .Net for 3 years, so cannot give you that solution myself, sorry :/
Another option with no need to play with json formatters or use string replacements - only inheritance and overriding (still not very nice solution, imo):
public class MyUser { }
public class MyItem { }
// you cannot use it out of the box, because it's abstract,
// i.e. only for what's intended [=implemented].
public abstract class PaginatedData<T>
{
// abstract, so you don't forget to override it in ancestors
public abstract IEnumerable<T> Data { get; }
public int Count { get; }
public int CurrentPage { get; }
public int Offset { get; }
public int RowsPerPage { get; }
public int? PreviousPage { get; }
public int? NextPage { get; }
}
// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
// explicit mapping - more agile than implicit name convension
[JsonProperty("Users")]
public override IEnumerable<MyUser> Data { get; }
}
public sealed class PaginatedItems : PaginatedData<MyItem>
{
[JsonProperty("Items")]
public override IEnumerable<MyItem> Data { get; }
}

Here is a solution that doesn't require any change in the way you use the Json serializer. In fact, it should also work with other serializers. It uses the cool DynamicObject class.
The usage is just like you wanted:
var usersPagedData = new PagedData<User>("Users");
....
public class PagedData<T> : DynamicObject
{
private string _name;
public PagedData(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
_name = name;
}
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
public override IEnumerable<string> GetDynamicMemberNames()
{
yield return _name;
foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
{
yield return prop.Name;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == _name)
{
result = Data;
return true;
}
return base.TryGetMember(binder, out result);
}
}

The following is another solution tested in .NET Standard 2.
public class PagedResult<T> where T : class
{
[JsonPropertyNameBasedOnItemClassAttribute]
public List<T> Results { get; set; }
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("total_count")]
public long TotalCount { get; set; }
[JsonProperty("current_page")]
public long CurrentPage { get; set; }
[JsonProperty("per_page")]
public long PerPage { get; set; }
[JsonProperty("pages")]
public long Pages { get; set; }
}
I am using Humanizer for pluralization.
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type[] arguments = property.DeclaringType.GenericTypeArguments;
if(arguments.Length > 0)
{
string name = arguments[0].Name.ToString();
property.PropertyName = name.ToLower().Pluralize();
}
return property;
}
return base.CreateProperty(member, memberSerialization);
}

There's a package called SerializationInterceptor. Here's the GitHub link: https://github.com/Dorin-Mocan/SerializationInterceptor/wiki. You can also install the package using Nuget Package Manager.
The example from below uses Syste.Text.Json for serialization. You can use any other serializer(except Newtonsoft.Json). For more info on why Newtonsoft.Json not allowed, please refer to GitHub documentation.
You can create an interceptor
public class JsonPropertyNameInterceptorAttribute : InterceptorAttribute
{
public JsonPropertyNameInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyNameAttribute))
{
}
protected override void Intercept(in AttributeParams originalAttributeParams, object context)
{
string theNameYouWant;
switch (InterceptorId)
{
case "some id":
theNameYouWant = (string)context;
break;
default:
return;
}
originalAttributeParams.ConstructorArgs.First().ArgValue = theNameYouWant;
}
}
And put the interceptor on the Data prop
public class PagedData<T>
{
[JsonPropertyNameInterceptor("some id")]
[JsonPropertyName("during serialization this value will be replaced with the one passed in context")]
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
And then you can serialize the object like this
var serializedObj = InterceptSerialization(
obj,
objType,
(o, t) =>
{
return JsonSerializer.Serialize(o, t, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve });
},
context: "the name you want");
Hope this will be of use to you.

have a look here:
How to rename JSON key
Its not done during serialization but with a string operation.
Not very nice (in my eyes) but at least a possibility.
Cheers Thomas

Related

Deserializing a JSON array with awkward additional property in the schema

I have a very similar issue to this question here, except my application is in C#, and I can't figure out how to convert the solution unfortunately. I am trying to deserialize a JSON result that looks like this:
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77],
],
"last":1619118000
}
I use the following classes:
public class ResponseBase
{
[JsonProperty(PropertyName = "error")]
public List<string> Error;
}
public class OHLCResponse : ResponseBase
{
[JsonProperty("result")]
public OHLCResult Result;
}
public class OHLCResult
{
[JsonProperty("pair_names")]
public Dictionary<string, OHLC[]> GetHistory;
[JsonProperty("last")]
public long Last;
}
.... and then finally the guts of it:
public class OHLC
{
public int Time;
public decimal Open;
public decimal High;
public decimal Low;
public decimal Close;
public decimal Vwap;
public decimal Volume;
public int Count;
}
I have a standard deserializer class which works for all other calls I am using to the same API, but I cannot get this call to work. When I retrieve OHLCResponse object,I don't get an error, and "Result.Last" is always populated, but the expected array of OHLC items in "Result.GetHistory" is always empty/null. I know that the data has been returned successfully since I can see the data in the variable returned from the WebRequest that I am then passing to the deserializer function, so it must be that I have these classes laid out incorrectly I guess.
Can anyone see what I'm doing wrong?
Many thanks in advance, Dave
The object you posted isn't valid JSON. The outside curly braces are missing. So I am going to assume it should look like this:
{
"error": [],
"result": {
"MANAEUR": [
[1619042400, "1.11200", "1.13488", "1.08341", "1.10077", "1.09896", "58878.56534370", 137],
[1619046000, "1.09767", "1.12276", "1.08490", "1.11097", "1.10456", "25343.25910419", 77],
],
"last": 1619118000
}
}
Anonymous Deserialization
The first method you could do, which may be a tad kludgy since you have to deserialize twice, is use anonymous deserialization.
Let's start by defining some models:
public sealed class OHLCModel
{
public long Time { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
public decimal Vwap { get; set; }
public decimal Volume { get; set; }
public int Count { get; set; }
}
public sealed class ResultModel
{
[JsonIgnore]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
public sealed class RootModel
{
[JsonProperty("error")]
public List<string> Error { get; set; }
[JsonProperty("result")]
public ResultModel Result { get; set; }
}
As you can see we are ignoring the Manaeur object when serialization happens.
To make this method work, we'd do this:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
// First, just grab the object that has the mixed arrays.
// This creates a "template" of the format of the target object
var dto = JsonConvert.DeserializeAnonymousType(json, new
{
Result = new
{
Manaeur = new List<List<object>>()
}
});
// Next, deserialize the rest of it
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
// transfer the DTO using a Select statement
fullObject.Result.Manaeur = dto.Result.Manaeur.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
This isn't the most ideal solution as you are tightly coupling to the model in a few spots. The ideal way to do this would be to make a custom JsonSerializer.
Use a Custom JsonConverter
First thing we do is change your ResultModel to look like this:
public sealed class ResultModel
{
[JsonConverter(typeof(ManaeurJsonConverter)), JsonProperty("MANAEUR")]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
Then implement a JsonConverter:
public sealed class ManaeurJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => false; // this will never get called
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var lst = JArray.Load(reader).ToObject<List<List<object>>>();
return lst.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ // we don't need to write
throw new NotImplementedException();
}
}
You can then simply call it as so:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
Your JSON is not valid, I had to modify it to make it valid JSON (https://jsonformatter.org/). I added the root brackets and removed the comma delimter after the second inner array entry.
Valid JSON:
{
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77]
],
"last":1619118000
}
}
After updating the JSON, I used Visual Studio's 'Paste Special' to generate C# objects from the JSON. The following classes were created.
public class RootObject
{
[JsonProperty("error")]
public object[] Error { get; set; }
[JsonProperty("result")]
public Result Result { get; set; }
}
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
With the above JSON and classes, I used the following to deserialize the JSON.
string json = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var obj = JsonConvert.DeserializeObject<RootObject>(json);
EDIT:
To handle the MANAEUR property where the key label can be different.
Create a JsonConverter...
public class ManaeurConverter : JsonConverter
{
private Dictionary<string, string> propertyMappings { get; set; }
public ManaeurConverter()
{
this.propertyMappings = new Dictionary<string, string>
{
{"NOTMANAEUR","MANAEUR"}
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = Activator.CreateInstance(objectType);
var props = objectType.GetTypeInfo().DeclaredProperties.ToList();
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
if (!propertyMappings.TryGetValue(jp.Name, out var name))
name = jp.Name;
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override bool CanConvert(Type objectType)
{
return objectType.GetTypeInfo().IsClass;
}
public override bool CanWrite => false;
}
... Add the JsonConverter attribute to the class...
[JsonConverter(typeof(ManaeurConverter))]
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
... and parse like so...
string json_Manaeur = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
string json_Not_Manaeur = "{\"error\":[],\"result\":{\"NOTMANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var objManaeur = JsonConvert.DeserializeObject<RootObject>(json_Manaeur);
var objNotManaeur = JsonConvert.DeserializeObject<RootObject>(json_Not_Manaeur);

How Can I Parse YAML Into a Derived Collection Using YamlDotNet?

Using YamlDotNet, I am attempting to deserialize the following YAML:
Collection:
- Type: TypeA
TypeAProperty: value1
- Type: TypeB
TypeBProperty: value2
The Type property is a required property for all objects under Collection. The rest of the properties are dependent on the type.
This is my ideal object model:
public class Document
{
public IEnumerable<IBaseObject> Collection { get; set; }
}
public interface IBaseObject
{
public string Type { get; }
}
public class TypeAClass : IBaseObject
{
public string Type { get; set; }
public string TypeAProperty { get; set; }
}
public class TypeBClass : IBaseObject
{
public string Type { get; set; }
public string TypeBProperty { get; set; }
}
Based on my reading, I think my best bet is to use a custom node deserializer, derived from INodeDeserializer. As a proof of concept, I can do this:
public class MyDeserializer : INodeDeserializer
{
public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object> nestedObjectDeserializer, out object value)
{
if (expectedType == typeof(IBaseObject))
{
Type type = typeof(TypeAClass);
value = nestedObjectDeserializer(parser, type);
return true;
}
value = null;
return false;
}
}
My issue now is how to dynamically determine the Type to choose before calling nestedObjectDeserializer.
When using JSON.Net, I was able to use a CustomCreationConverter, read the sub-JSON into a JObject, determine my type, then create a new JsonReader from the JObject and re-parse the object.
Is there a way I can read, roll-back, then re-read nestedObjectDeserializer?
Is there another object type I can call on nestedObjectDeserializer, then from that read the Type property, finally proceed through normal YamlDotNet parsing of the derived type?
It's not easy. Here is an GitHub issue explaining how to do polymorphic serialization using YamlDotNet.
A simple solution in your case is to to 2-step deserialization. First you deserialize into some intermediary form and than convert it to your models. That's relatively easy as you limit digging in the internals of YamlDotNet:
public class Step1Document
{
public List<Step1Element> Collection { get; set; }
public Document Upcast()
{
return new Document
{
Collection = Collection.Select(m => m.Upcast()).ToList()
};
}
}
public class Step1Element
{
// Fields from TypeA and TypeB
public string Type { get; set; }
public string TypeAProperty { get; set; }
public string TypeBProperty { get; set; }
internal IBaseObject Upcast()
{
if(Type == "TypeA")
{
return new TypeAClass
{
Type = Type,
TypeAProperty = TypeAProperty
};
}
if (Type == "TypeB")
{
return new TypeBClass
{
Type = Type,
TypeBProperty = TypeBProperty
};
}
throw new NotImplementedException(Type);
}
}
And that to deserialize:
var serializer = new DeserializerBuilder().Build();
var document = serializer.Deserialize<Step1Document>(data).Upcast();

Json.NET CamelCasePropertyNameContractResolver not working with private property setters

I am using Json.NET (8.0.3) and I am trying to use the CamelCasePropertyNameContractResolver with JsonConvert.DeseralizeObject() so that I can read JSON with camel case properties. Here is an example of the JSON.
{ "name":"somename", "type":"sometype" }
Here is the class I am trying to deserialize to:
public class MyClass {
public string Name { get; private set; }
public string Type { get; private set; }
}
If I use JsonConvert.DeseralizeObject the Name and Type values are null because technically the class property names do not match the JSON property names. This was expected. If I add the JsonProperty attribute then it will deserialize correctly (also expected).
public class MyClass {
[JsonProperty("name")]
public string Name { get; private set; }
[JsonProperty("type")]
public string Type { get; private set; }
}
I do not want to put the JsonProperty attribute on all of the properties so I tried the CamelCasePropertyNameContractResolver.
JsonConvert.DefaultSettings = () => new JsonSerialierSettings {
ContractResolver = new CamelCasePropertyNameContractResolver()
};
MyClass value = JsonConvert.DeserializeObject<MyClass>(json);
The Name and Type properties of the MyClass object are both null which was unexpected. If I make the setter public then it works correctly.
public class MyClass {
public string Name { get; set; }
public string Type { get; set; }
}
The obvious answer here is to just keep the setter public, but if I want/need to have the setter private, how can I get the CamelCasePropertyNameContractResolver to work with private setters? Am I doing something wrong, or is this a possible bug?
You can do it by writing a custom ContractResolver
string json = #"{""name"":""somename"", ""type"":""sometype"" }";
var settings = new JsonSerializerSettings() {
ContractResolver = new AllPropertiesContractResolver() };
var res = JsonConvert.DeserializeObject<MyClass>(json,settings);
public class MyClass
{
public string Name { get; private set; }
public string Type { get; private set; }
}
public class AllPropertiesContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.Select(x => new Newtonsoft.Json.Serialization.JsonProperty()
{
PropertyName = x.Name,
PropertyType = x.PropertyType,
Readable = true,
ValueProvider = new AllPropertiesValueProvider(x),
Writable = true
})
.ToList();
return props;
}
}
public class AllPropertiesValueProvider : Newtonsoft.Json.Serialization.IValueProvider
{
PropertyInfo _propertyInfo;
public AllPropertiesValueProvider(PropertyInfo p)
{
_propertyInfo = p;
}
public object GetValue(object target)
{
return _propertyInfo.GetValue(target); //Serialization
}
public void SetValue(object target, object value)
{
_propertyInfo.SetValue(target, value, null); //Deserialization
}
}
BTW: If I use JsonConvert.DeseralizeObject the Name and Type values are null because technically the class property names do not match the JSON property names. is not correct. If your properties had public setters and getters, deserialization would ignore the cases when using default settings (this is what I use in this answer. My ContractResolver additionally includes private properties in deserialization process; that is all).....
See the other question I used the same ContractResolver: What am I doing wrong with JSON.NET's JsonConvert

Dispaying and editing a sub property of a property in PropertyGrid

I could no longer find an exact solution to my problem in the internet so I'm asking this question. Hope you may be able to help me.
I have the following classes:
public Item
{
public FieldType MyField { get; set; }
public string Description { get; set; }
public int Capacity { get; set; }
}
public FieldType
{
public string Value { get; set; }
public string FieldCode { get; set; }
public string TableCode { get; set; }
}
In my form, I created an instance of Item class. Which contains the following members:
MyField (type of FieldType)
Description (type of string)
Capacity (an int)
Is it possible to only show the Value member of MyField property in the PropertyGrid?
Below is how I assign the selected object property of the PropertyGrid.
void Form1(object sender, EventArgs e)
{
propertyGrid1.SelectedObject = new Item();
}
Yes, easy:
add a computed read only property to Item
public Item
{
public FieldType MyField { get; set; }
public string MyFieldValue => MyField.Value;
public string Description { get; set; }
public int Capacity { get; set; }
}
Im not really sure of what you are looking for but here are 2 answers
1.(as I understood it)
if you want it to show only Value when you try and view the properties of a MyField Instance then all you need to do is add a constructor to the MyField so you can assign the other two values and change the public property to private like so
public FieldType
{
public string Value { get; set; }
private string FieldCode { get; set; }
private string TableCode { get; set; }
}
2.(this will hide the MyField from your propertyGrid)
Override the ToString() method of FielType
like so
public override string ToString()
{
return Value;
}
then set your MyField to private and encapsulate it. returning the instance as a string. which would use the overridden value.
like so
private FieldType MyField;
public string value{ get{return MyField.ToString();}set;}
your MyField will return the overridden ToString value which returns Value.
Solution 1 - Add a property
You can add a property to Item class to get and set MyField.Value:
public string Value
{
get
{
if (MyField != null)
return MyField.Value;
return null;
}
set
{
if (MyField != null)
MyField.Value = value;
}
}
• Preferably define that property in a partial class.
• Use this option when you have access to codes of the classes. If those classes are not yours, use 3rd solution.
Solution 2 - Use ExpandableObjectConverter
You can decorate the MyField property of Item class with ExpandableObjectConverter. Also decorate FieldType with [Browsable(false)] of FieldType class to hide it in property grid if you want:
[TypeConverter(typeof(ExpandableObjectConverter))]
public FieldType MyField { get; set; }
• To customize the text which is shown in front of MyField, you can override ToString method of FieldType and return Value. Also you can do it using a custom TypeConverter and overriding its ConvertTo method.
Solution 3 - Use a custom TypeDescriptor
It's not as easy as the first solution, but the output is completely like what you get using the first solution. It's suitable for cases that you can not manipulate those classes.
You can use it this way:
var item = new Item() { MyField = new FieldType() { Value = "Some Value" } };
TypeDescriptor.AddProvider(new MyTypeDescriptionProvider(), item);
this.propertyGrid1.SelectedObject = item;
Or by decorating Item class with:
[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]
public class Item
Custom Property Descriptor
public class MyPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor subProperty;
private PropertyDescriptor parentProperty;
public MyPropertyDescriptor(PropertyDescriptor parent, PropertyDescriptor sub)
: base(sub, null)
{
subProperty = sub;
parentProperty = parent;
}
public override bool IsReadOnly { get { return subProperty.IsReadOnly; } }
public override void ResetValue(object component)
{
subProperty.ResetValue(parentProperty.GetValue(component));
}
public override bool CanResetValue(object component)
{
return subProperty.CanResetValue(parentProperty.GetValue(component));
}
public override bool ShouldSerializeValue(object component)
{
return subProperty.ShouldSerializeValue(parentProperty.GetValue(component));
}
public override Type ComponentType { get { return parentProperty.ComponentType; } }
public override Type PropertyType { get { return subProperty.PropertyType; } }
public override object GetValue(object component)
{
return subProperty.GetValue(parentProperty.GetValue(component));
}
public override void SetValue(object component, object value)
{
subProperty.SetValue(parentProperty.GetValue(component), value);
OnValueChanged(component, EventArgs.Empty);
}
}
Custom type Descriptor
public class MyTypeDescriptor : CustomTypeDescriptor
{
ICustomTypeDescriptor original;
public MyTypeDescriptor(ICustomTypeDescriptor originalDescriptor)
: base(originalDescriptor)
{
original = originalDescriptor;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = original.GetProperties().Cast<PropertyDescriptor>().ToList();
var parent = properties.Where(x => x.Name == "MyField").First();
var sub = TypeDescriptor.GetProperties(typeof(FieldType))["Value"];
properties.Remove(parent);
properties.Add(new MyPropertyDescriptor(parent, sub));
return new PropertyDescriptorCollection(properties.ToArray());
}
}
Custom TypeDescriptorProvider
public class MyTypeDescriptionProvider : TypeDescriptionProvider
{
public MyTypeDescriptionProvider()
: base(TypeDescriptor.GetProvider(typeof(object))) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance)
{
ICustomTypeDescriptor baseDes = base.GetTypeDescriptor(objectType, instance);
return new MyTypeDescriptor(baseDes);
}
}
• Use this option if Item and FieldType are not yours. If those classes are yours and you can change their code, use first solution.

How can I use Json.net to populate a model with custom bindings/mappings?

Here's my JSON:
{
"macAddress": "000A959D6816",
"softwareVersion": "1.2.1.5-UnnecessaryInfo",
"lastUpdated": "2015-04-03 20:46:40.375 -0500",
"charging": true
}
And using Json.NET, I can do the following in C#:
namespace JsonTest
{
public class Tablet
{
public string MacAddress { get; set; }
public string SoftwareVersion { get; set; }
public DateTime LastUpdated { get; set; }
public bool Charging { get; set; }
}
public class TestClass
{
public void Test()
{
var json = "{ ... }"; // filled in with JSON info from above
var model = new Tablet();
try
{
JsonConvert.PopulateObject(json, model);
}
catch (JsonSerializationException ex)
{
Console.WriteLine(ex);
}
}
}
}
So far, so good. The code I have here works great. It populates my model object with all the data from the Json. However, I don't really want the SoftwareVersion property of my model to be a string; I'd rather have it be an instance of the System.Version class. In other words, I'd like my Tablet class to look more like this:
public class Tablet
{
public string MacAddress { get; set; }
public Version SoftwareVersion { get; set; }
public DateTime LastUpdated { get; set; }
public bool Charging { get; set; }
}
I don't care about the unnecessary info that gets appended on the end of that version string, so I'd like to write some sort of mapper/binder class that examines the version string from the Json, strips off the unnecessary info, and then parses that field into a Version object before proceeding to populate my model. I do know how to write that individual parsing method; this would do the trick:
private static Version ParseVersion(object versionObj)
{
var pattern = new Regex(#"^[\d.]+");
var versionString = versionObj.ToString();
if (!pattern.IsMatch(versionString)) return null;
var match = pattern.Match(versionString);
versionString = match.Groups[0].ToString();
Version version;
Version.TryParse(versionString, out version);
return version;
}
What I don't know is where and how to "plug this in" during the JsonConvert process. I see that the PopulateObject takes an optional JsonSerializerSettings parameter, and in turn that has several different object initializer parameters like Binder and Converters. But I'm not sure which one to use, nor how to write either of those classes to do what I'm describing here. How do I do it? And what is the difference between a Binder and a Converter?
Just add an appropriate JsonConverterAttribute to your Version property, and PopulateObject will use it:
public class Tablet
{
public string MacAddress { get; set; }
[JsonConverter(typeof(VersionConverter))]
public Version SoftwareVersion { get; set; }
public DateTime LastUpdated { get; set; }
public bool Charging { get; set; }
}
And here is the actual converter:
public class VersionConverter : JsonConverter
{
private static Version ParseVersion(object versionObj)
{
var pattern = new Regex(#"^[\d.]+");
var versionString = versionObj.ToString();
if (!pattern.IsMatch(versionString))
return null;
var match = pattern.Match(versionString);
versionString = match.Groups[0].ToString();
Version version;
Version.TryParse(versionString, out version);
return version;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(System.Version);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return ParseVersion((string)token);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var version = (Version)value;
if (version != null)
writer.WriteValue(value.ToString());
}
}
You should now be all set.
Alternatively, if you have a Version property appearing in many different container classes in a complex object graph, and you don't want to set the JsonConverterAttribute everywhere, you can add your converter to JsonSerializerSettings.Converters, then pass that to PopulateObject:
JsonConvert.PopulateObject(json, model, new JsonSerializerSettings { Converters = new JsonConverter [] { new VersionConverter() } } );

Categories

Resources