My application receives a JSON object as:
{
"names": [
"name_1",
"name_2"
]
}
I want to deserialize it to a list of Person object, defined as:
class Person
{
public string Name { set; get; }
}
Currently, I am deserializing the JSON object to a list of strings then create a list of Person manually like the following:
var names = JsonConvert.DeserializeObject<List<string>>(json);
var people = new List<Person>();
foreach(var name in names)
people.Add(new Person(){ Name = name });
Instead, I am interested in something like the following:
var people = JsonConvert.DeserializeObject<List<Person>>(json);
I am implementing the Person deserializer as:
public class PersonJsonConverter : JsonConverter
{
private readonly Dictionary<string, string> _propertyMappings;
public PersonJsonConverter()
{
_propertyMappings = new Dictionary<string, string>
{
{"name", nameof(Person.Name)}
};
}
public override bool CanConvert(Type objectType)
{
return objectType.GetTypeInfo().IsClass;
}
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
object instance = Activator.CreateInstance(objectType);
var props = objectType.GetTypeInfo().DeclaredProperties.ToList();
JObject obj = JObject.Load(reader);
foreach (JProperty jsonProperty in obj.Properties())
{
if (!_propertyMappings.TryGetValue(jsonProperty.Name, out var name))
name = jsonProperty.Name;
PropertyInfo prop = props.FirstOrDefault(
pi => pi.CanWrite && pi.Name == name);
prop?.SetValue(
instance,
jsonProperty.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
}
This deserializer can deserilize object such as:
{
"names": [
"name": "name_1",
"name": "name_2"
]
}
but not
{
"names": [
"name_1",
"name_2"
]
}
Note that my application can receive both types of JSON objects, so better to have a common deserializer for both types.
Please try below:
class Person
{
public string Name { set; get; }
}
public class PersonJsonConverter : JsonConverter
{
// private readonly Dictionary<string, string> _propertyMappings;
public PersonJsonConverter()
{
/*_propertyMappings = new Dictionary<string, string>
{
{"name", nameof(Person.Name)}
};*/
}
public override bool CanConvert(System.Type objectType)
{
return objectType.GetTypeInfo().IsClass;
}
public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = Activator.CreateInstance(objectType);
// List<T> implements the non-generic IList interface
IList list = (IList)instance;
var typeInfo = objectType.GetTypeInfo();
var props = typeInfo.DeclaredProperties.ToList();
PropertyInfo prop = props.FirstOrDefault(pi => pi.PropertyType == typeof(Person));
JObject obj = JObject.Load(reader);
var namesArray = obj["names"]; // you can use this instead of for loop on obj.Properties.
/*foreach (JProperty jsonProperty in obj.Properties())
{
if (jsonProperty.Name == "names")
{
var namesArray = JArray.Parse(jsonProperty.Value.ToString());
*/
if (namesArray.Type == JTokenType.Array && prop != null)
{
foreach (var ja in namesArray)
{
object personInstance = Activator.CreateInstance(prop.PropertyType);
PropertyInfo personNamePropInfo = prop.PropertyType.GetProperty(nameof(Person.Name));
personNamePropInfo.SetValue(personInstance,
Convert.ChangeType(ja, personNamePropInfo.PropertyType), null);
list.Add(personInstance); // Whatever you need to add
}
}
/* break;
}
}*/
return instance;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
and use it like below:
var samplejson = #"{
""names"": [
""name_1"",
""name_2""
]
}";
var obj = JsonConvert.DeserializeObject<List<Person>>(samplejson, new JsonConverter[] { new PersonJsonConverter() });
The other json does not seem to be valid json.
You have to deserialize names to something like a JArray, then foreach each entry with a new Person('name'). The JSON as you received it just doesn't match your desired schema. You have to manually transform it.
Related
I am just wonder if I can mark certain property of class instance via any attribute and during serialization serialize just those marked properties (and of-course by deserializing affect also only marked properties via attribute vice-versa in instance of the class - the rest of properties should remain same...).
I know how to identify those properties by reflection, but I do not want to make another Json serialization by myself.
[MyFirstAttribute]
public string A { get; set; } = "hi";
[MyFirstAttribute]
public int B { get; set; } = 13;
[MySecondAttribute]
public string C { get; set; } = "something";
as it documented here in this link you can create a custom CustomJsonConverter by inheriting from JsonConverter class.
And then use it like:
Employee employee = new Employee
{
FirstName = "James",
LastName = "Newton-King",
Roles = new List<string>
{
"Admin"
}
};
string json = JsonConvert.SerializeObject(employee, Formatting.Indented, new KeysJsonConverter(typeof(Employee)));
Console.WriteLine(json);
Based on #ArgeKumandan advice:
public class KeysJsonConverter : JsonConverter
{
private readonly Type[] _types;
public KeysJsonConverter(params Type[] types) => _types = types;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object) t.WriteTo(writer);
else
{
JObject jo = new JObject();
foreach (PropertyInfo prop in value.GetType().GetProperties())
{
if (!prop.CanRead) continue;
object propVal = prop.GetValue(value, null);
if (propVal is null || !Attribute.IsDefined(prop, _types[0])) continue;
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
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 => false; }
public override bool CanConvert(Type objectType) => _types.Any(t => t == _types[0]);
}
and then usage:
// serialization
var json = JsonConvert.SerializeObject(objectInstance1, Formatting.Indented, new KeysJsonConverter(typeof(MyFirstAttribute)));
// deserialization to an existing instance that updates just the properties contained in JSON
JsonConvert.PopulateObject(jsonCommon, objectInstance2);
I'm looking for a way to transform this c# object:
class BaseClass
{
public string Value1 {get; set;}
public NestedObject nestedObject {get;set;}
}
class NestedObject
{
public string NestedValue1 {get; set;}
}
Into this json:
{
"Value1": "value1",
"NestedObject_NestedValue1": "nestedValue1"
}
By concatening the names of the nested parameters to their parent's name
Using normal serialization, this code: var json= JsonConvert.SerializeObject(baseClass);
Would instead return a json like this one:
{
"Value1": "value1",
"NestedObject": {
"NestedValue1": "nestedValue1"
}
}
I am sceptical about there being a way to deserialize a json like that back to an object tho.
Update:
As some asked what is the reason I'm trying to accomplish this:
The reason I asked this question is because I serialize this object to send as json metadata to a service that only allows referencing top level propreties in a way similar to this:
[Metadata_Value1] would return "value1"
However [Metadata_NestedObject_NestedValue1] doesn't work and there isn't any indication to there being a way to reference nested properties.
Taking this in consideration I hoped there would be some solution that would allow keeping the nested objects in my program but transforming them all to top properties when sending them to this service.
In the service I would then be able to do: [NestedObject_NestedValue1] and get the value "nestedValue1"
You can utilize a custom converter that looks like below :
public class NestedJsonConverter : JsonConverter
{
private readonly Type[] _types;
public NestedJsonConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
writer.WriteStartObject();
void writeNested(JObject target, object source, string prefix)
{
target.Properties().ToList().ForEach(p =>
{
var prop = source.GetType().GetProperty(p.Name);
var value = prop.GetValue(source);
var prefixed = string.IsNullOrEmpty(prefix) ? p.Name : $"{prefix}_{p.Name}";
if (p.Value.Type == JTokenType.Object)
{
writeNested((JObject)p.Value, value, prefixed);
}
else if (p.Value.Type == JTokenType.Array)
{
// you may need a more advanced handling in array scenarios
var arr = (JArray)p.Value;
writer.WritePropertyName(prefixed);
arr.WriteTo(writer);
}
else
{
writer.WritePropertyName(prefixed);
writer.WriteValue(value);
}
}
);
}
writeNested(o, value, "");
writer.WriteEndObject();
}
}
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 _types.Any(t => t == objectType);
}
}
Dotnetfiddle
I am receiving data that looks like this from an online service provider:
{
name: "test data",
data: [
[ "2017-05-31", 2388.33 ],
[ "2017-04-30", 2358.84 ],
[ "2017-03-31", 2366.82 ],
[ "2017-02-28", 2329.91 ]
],
}
I would like to parse it into an object that looks like this:
public class TestData
{
public string Name;
public List<Tuple<DateTime, double>> Data;
}
The only thing I have been able to find is how to parse an array of objects into a list of tulples, for example: Json.NET deserialization of Tuple<...> inside another type doesn't work?
Is there a way to write a custom converter that would handle this?
If anyone is interested in a more generic solution for ValueTuples
public class TupleConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = value.GetType();
var array = new List<object>();
FieldInfo fieldInfo;
var i = 1;
while ((fieldInfo = type.GetField($"Item{i++}")) != null)
array.Add(fieldInfo.GetValue(value));
serializer.Serialize(writer, array);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var argTypes = objectType.GetGenericArguments();
var array = serializer.Deserialize<JArray>(reader);
var items = array.Select((a, index) => a.ToObject(argTypes[index])).ToArray();
var constructor = objectType.GetConstructor(argTypes);
return constructor.Invoke(items);
}
public override bool CanConvert(Type type)
{
return type.Name.StartsWith("ValueTuple`");
}
}
Usage is as follows:
var settings = new JsonSerializerSettings();
settings.Converters.Add(new TupleConverter());
var list = new List<(DateTime, double)>
{
(DateTime.Now, 7.5)
};
var json = JsonConvert.SerializeObject(list, settings);
var result = JsonConvert.DeserializeObject(json, list.GetType(), settings);
Rather than use tuples, I would create a class that is specific to the task. In this case your JSON data comes in as a list of lists of strings which is a bit awkward to deal with. One method would be to deserialise as List<List<string>> and then convert afterwards. For example, I would go with 3 classes like this:
public class IntermediateTestData
{
public string Name;
public List<List<string>> Data;
}
public class TestData
{
public string Name;
public IEnumerable<TestDataItem> Data;
}
public class TestDataItem
{
public DateTime Date { get; set; }
public double Value { get; set; }
}
Now deserialise like this:
var intermediate = JsonConvert.DeserializeObject<IntermediateTestData>(json);
var testData = new TestData
{
Name = intermediate.Name,
Data = intermediate.Data.Select(d => new TestDataItem
{
Date = DateTime.Parse(d[0]),
Value = double.Parse(d[1])
})
};
So using JSON.NET LINQ, I managed to get it to work as you prescribed...
var result = JsonConvert.DeserializeObject<JObject>(json);
var data = new TestData
{
Name = (string)result["name"],
Data = result["data"]
.Select(t => new Tuple<DateTime, double>(DateTime.Parse((string)t[0]), (double)t[1]))
.ToList()
};
This is the full test I wrote
public class TestData
{
public string Name;
public List<Tuple<DateTime, double>> Data;
}
[TestMethod]
public void TestMethod1()
{
var json =
#"{
name: ""test data"",
data: [
[ ""2017-05-31"", 2388.33 ],
[ ""2017-04-30"", 2358.84 ],
[ ""2017-03-31"", 2366.82 ],
[ ""2017-02-28"", 2329.91 ]
],
}";
var result = JsonConvert.DeserializeObject<JObject>(json);
var data = new TestData
{
Name = (string)result["name"],
Data = result["data"]
.Select(t => new Tuple<DateTime, double>(DateTime.Parse((string)t[0]), (double)t[1]))
.ToList()
};
Assert.AreEqual(2388.33, data.Data[0].Item2);
}
However, while this may work, I am in agreement with the rest of the comments/answers that using tuples for this is probably not the correct way to go. Using concrete POCO's is definitely going to be a hell of a lot more maintainable in the long run simply because of the Item1 and Item2 properties of the Tuple<,>.
They are not the most descriptive...
I took the generic TupleConverter from here: Json.NET deserialization of Tuple<...> inside another type doesn't work?
And made a generic TupleListConverter.
Usage:
public class TestData
{
public string Name;
[Newtonsoft.Json.JsonConverter(typeof(TupleListConverter<DateTime, double>))]
public List<Tuple<DateTime, double>> Data;
}
public void Test(string json)
{
var testData = JsonConvert.DeserializeObject<TestData>(json);
foreach (var tuple in testData.data)
{
var dateTime = tuple.Item1;
var price = tuple.Item2;
... do something...
}
}
Converter:
public class TupleListConverter<U, V> : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Tuple<U, V>) == objectType;
}
public override object ReadJson(
Newtonsoft.Json.JsonReader reader,
Type objectType,
object existingValue,
Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
var jArray = Newtonsoft.Json.Linq.JArray.Load(reader);
var target = new List<Tuple<U, V>>();
foreach (var childJArray in jArray.Children<Newtonsoft.Json.Linq.JArray>())
{
var tuple = new Tuple<U, V>(
childJArray[0].ToObject<U>(),
childJArray[1].ToObject<V>()
);
target.Add(tuple);
}
return target;
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
I have this kind of class:
class Thing
{
// ...
public IDictionary<string, dynamic> Parameters { get; set; }
}
//...
void Test()
{
Thing thing = new Thing();
thing.Paramaters["counting"] = new List<int>{1,2,3};
thing.Parameters["name"] = "Numbers";
thing.Parameters["size"] = 3;
string result = JsonConvert.SerializeObject(thing);
}
And I'd like to result to contain this:
{
"Parameters" :
{
"counting" : "[1,2,3]",
"name" : "Numbers",
"size" : 3
}
}
I have taken a look at IContractResolver and believe I should special case strings on deserialization to check if there's JSON in them, and special case all class objects to convert them to a string. I just have no idea where to begin doing that.
In the end the problem is this: the data structure I'm plugging this JSON into does not work with nested JSON. It only knows about the basic data, i.e. string and numbers, at this "sublevel". I know, terrible, get rid of this evil data structure. Well, I can't. So I need to be creative and I thought this might be a way out. If anyone can think of a better way, I'd much appreciate it!
EDIT The answers below special case the List<int> example I put in Test, but it's still a Dictionary<string, dynamic> which can contain everything. That's what I mean with nested JSON: any JSON, not just an array.
I understand the problem is not so much about writing a List to a string, but more about a way to create JSON with only two levels of depth - having everything beyond that a string.
I don't think an IContractResolver would work for this, you should implement a JsonConverter instead. The basic idea would be that it iterates over your object's children, then over their children, checking their type. If they're an array or an object - it would replace them with a serialized string.
Implementation:
class TwoDepthJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var jo = JObject.FromObject(value);
foreach (var property in jo)
{
foreach (var parameter in property.Value)
{
var paramVal = parameter.First;
if (paramVal.Type == JTokenType.Array || paramVal.Type == JTokenType.Object)
{
paramVal.Replace(JsonConvert.SerializeObject(paramVal.ToObject<object>()));
}
}
}
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JToken.ReadFrom(reader).ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Usage:
Thing thing = new Thing();
thing.Parameters["counting"] = new List<int> { 1, 2, 3 };
thing.Parameters["name"] = "Numbers";
thing.Parameters["size"] = 3;
string result = JsonConvert.SerializeObject(thing, Formatting.Indented, new TwoDepthJsonConverter());
// Results:
// {
// "Parameters": {
// "counting": "[1,2,3]",
// "name": "Numbers",
// "size": 3
// }
// }
Of course, performance could be improved - for example writing to the writer manually instead of parsing to a JObject and then manipulating it. However this should be a good starting point.
You can create a JsonConverter and instead of using List<T> you would use a custom class that inherits from List<T>..
The reason for it needs to be a custom List is that if you register a JsonConverter for List<T> you would not be able to get normal array serialization.
Following http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/ I was able to make this.
public class CustomListSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var values = (IEnumerable)value;
var items = values.Cast<object>().ToList();
var s = JsonConvert.SerializeObject(items);
writer.WriteValue(s);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(IEnumerable));
}
}
[JsonConverter(typeof(CustomListSerializer))]
internal class CustomList<T> : List<T>
{
}
class Program
{
static void Main(string[] args)
{
var parameters = new Dictionary<string, object>();
parameters.Add("counting", new CustomList<int>() { 1, 2, 3, 5 });
parameters.Add("users", new CustomList<User>() { new User { Name = "TryingToImprove" }, new User { Name = "rubenvb" } });
parameters.Add("name", "Numbers");
parameters.Add("size", 4);
var thing = new
{
Parameters = parameters,
Name = "THING",
Test = new List<int>() { 1, 2, 3}
};
Console.WriteLine(JsonConvert.SerializeObject(thing));
}
}
internal class User
{
public string Name { get; set; }
}
which will return
{
"Parameters": {
"counting": "[1,2,3,5]",
"users": "[{\"Name\":\"TryingToImprove\"},{\"Name\":\"rubenvb\"}]",
"name": "Numbers",
"size": 4
},
"Name": "THING",
"Test": [1, 2, 3]
}
This is working -
internal class CustomJsonFormatter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(Thing));
}
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 data = value as Thing;
foreach (var prop in data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
writer.WriteStartObject();
writer.WritePropertyName(prop.Name);
writer.WriteStartObject();
var local = prop.GetValue(data, null) as Dictionary<string, object>;
foreach (var key in local.Keys)
{
writer.WritePropertyName(key);
if (local[key].GetType() == typeof(List<int>))
{
string s = "[";
var arr = ((List<int>)local[key]);
for (var i = 0; i < arr.Count; i++)
{
s += arr[i].ToString() + ",";
}
writer.WriteValue(s.Substring(0, s.Length - 1) + "]");
}
else { writer.WriteValue(local[key]); }
}
}
writer.WriteEndObject();
writer.WriteEndObject();
}
var settings = new JsonSerializerSettings();
settings.Converters.Add(new CustomJsonFormatter());
string result = JsonConvert.SerializeObject(thing, settings);
produces
{"Parameters":{"counting":"[1,2,3]","name":"Numbers","size":3}}
Using Json.NET in C#, I am having troubles when serializing a class, in which I have a need for a custom property name.
This is what I have now:
{
"baseName": {
"subName1": {
"name": null,
"longName": null,
"asd1": null,
"asd2": null
},
"subName2": [
{
"id": "ID_NUMBER",
"info": {
"someInfo1": "asd",
"someInfo2": "asd2"
}
}
]
}
}
This is what I want:
{
"baseName": {
"subName1": {
"name": null,
"longName": null,
"asd1": null,
"asd2": null
},
"subName2": [
{
"ID_NUMBER": {
"someInfo1": "asd",
"someInfo2": "asd2"
}
}
]
}
}
That is, instead of having a key with id and value ID_NUMBER, I want the ID_NUMBER to be the key of the object containing the keys someInfo1 and someInfo2.
The top JSON code is generated using these classes (sorry for the bad names):
class JSONTestClass
{
public JSONBaseTestClass baseName;
}
class JSONBaseTestClass
{
public JSONSubTestClass1 subName1;
public List<JSONSubTestClass2> subName2;
}
class JSONSubTestClass1
{
public string name;
public string longName;
public string asd1;
public string asd2;
}
class JSONSubTestClass2
{
public string id;
public JSONInfoTestClass info;
}
class JSONInfoTestClass
{
public string someInfo1;
public string someInfo2;
}
And this:
private void MakeJSON()
{
// This value can be changed at runtime
string specificId = "ID_NUMBER";
JSONInfoTestClass jitc = new JSONInfoTestClass();
jitc.someInfo1 = "asd";
jitc.someInfo2 = "asd2";
JSONTestClass jtc = new JSONTestClass();
JSONBaseTestClass jbtc = new JSONBaseTestClass();
JSONSubTestClass1 jstc1 = new JSONSubTestClass1();
JSONSubTestClass2 jstc2 = new JSONSubTestClass2();
jstc2.id = specificId;
jstc2.info = jitc;
List<JSONSubTestClass2> list = new List<JSONSubTestClass2>();
list.Add(jstc2);
jbtc.subName1 = jstc1;
jbtc.subName2 = list;
jtc.baseName = jbtc;
// Convert to JSON
string json = JsonConvert.SerializeObject(jtc, Formatting.Indented);
tbxJSONOutput.Text = json;
}
Which changes are needed so I can get a JSON output corresponding to the second JSON response mentioned above?
You can get the output you want by creating a custom JsonConverter for your JSONSubTestClass2 class like this:
class JSONSubTestClass2Converter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(JSONSubTestClass2));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JSONSubTestClass2 jstc2 = (JSONSubTestClass2)value;
JObject jo = new JObject();
jo.Add(jstc2.id, JObject.FromObject(jstc2.info));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, serialize your classes like this:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new JSONSubTestClass2Converter());
settings.Formatting = Formatting.Indented;
// Convert to JSON
string json = JsonConvert.SerializeObject(jtc, settings);