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);
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();
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
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.
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() } } );