I'm trying to deserialize some json that looks like this
{
"id":"2021",
"descriptions_bg":[
"30231300",
"30233160",
"32420000",
"30214000"
],
"descriptions_cs":[
"30231300",
"30233160",
"32420000",
"30214000"
],
"descriptions_da":[
"30231300",
"30233160",
"32420000",
"30214000"
],
"official_title_bg":"П",
"official_title_cs":"P",
"official_title_da":"P",
"auth_town_bg":"AuthTown",
"auth_town_cs":"AuthTown",
"auth_town_da":"AuthTown"
}
The problem here being, that there can come an infinite number of items in both descriptions_*, official_title_* and auth_town_* with different endings.
I've tried to make classes and members like public string official_title_* { get; set; }in C# to deserialize to with Newtonsoft Json, but that (of course) doesn't work.
Anyway to fix this?
The best option for you is to deserialize to Dictionary<string, JToken> and based on Key and Object Type you can write business logic. With the combination of Linq you can filter the Key
var json = File.ReadAllText("json1.json");
var dict = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(json);
foreach(var item in dict.Where(x=>x.Key.StartsWith("descriptions_")))
{
Console.WriteLine($"{item.Key} is type {item.Value.GetType()}");
}
foreach (var item in dict.Where(x => x.Key.StartsWith("auth_town_")))
{
Console.WriteLine($"{item.Key} is type {item.Value.GetType()}");
}
An alternative method, using a class object that defines each group of properties that have a common prefix as a Dictionary<string, TValue>.
The JSON is deserialized using a custom JsonConverter that maps each group of properties in the JSON to a Property of the class, using a Dictionary<string, string>:
Dictionary<string, string> map = new Dictionary<string, string>() {
["Descriptions"] = "descriptions_",
["OfficialTitles"] = "official_title_",
["AuthTowns"] = "auth_town_"
};
All properties in the JSON that map to a Property in the class are added to the corresponding Dictionary and the values converted to the Type defined by the class property.
Note: you need to adapt the mapper and class Properties to the JSON. You could make the procedure more generic using some logic to determine when Property Name in the JSON belongs to the same group and generate a new Dictionary<string, TValue> to add to the class by reflection.
Or simply add more Properties to the class off-line, if you find other groups.
Call it as:
(possibly giving the class a less silly name :)
var result = new SimpleSequences().Deserialize(json);
Helper class object:
private class SimpleSequences
{
public SimpleSequences() {
Descriptions = new Dictionary<string, long[]>();
OfficialTitles = new Dictionary<string, string>();
AuthTowns = new Dictionary<string, string>();
}
public List<SimpleSequences> Deserialize(string json)
{
var options = new JsonSerializerSettings() {
Converters = new[] { new SimpleSequencesConverter() }
};
return JsonConvert.DeserializeObject<List<SimpleSequences>>(json, options);
}
public int Id { get; set; }
public Dictionary<string, long[]> Descriptions { get; set; }
public Dictionary<string, string> OfficialTitles { get; set; }
public Dictionary<string, string> AuthTowns { get; set; }
}
Custom JsonConverter:
This converter handles a single object or array of objects (as shown in your question), but always return a List<SimpleSequences> (which may contain a single object). Modify as required.
public class SimpleSequencesConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray && reader.TokenType != JsonToken.StartObject) {
throw new Exception($"Unexpected Type. Expecting Array or Object, got {reader.TokenType}");
}
var elementsList = new List<SimpleSequences>();
if (reader.TokenType == JsonToken.StartObject) {
elementsList.Add(GetPropertyValues(JObject.Load(reader)));
}
else {
while (reader.Read() && reader.TokenType != JsonToken.EndArray) {
elementsList.Add(GetPropertyValues(JObject.Load(reader)));
}
}
return elementsList;
}
private SimpleSequences GetPropertyValues(JToken token)
{
var element = new SimpleSequences();
element.Id = token["id"].ToObject<int>();
var descriptions = token.Children().Where(t => t.Path.StartsWith(map["Descriptions"]));
foreach (JProperty p in descriptions) {
element.Descriptions.Add(p.Path, p.Value.ToObject<long[]>());
}
var titles = token.Children().Where(t => t.Path.StartsWith(map["OfficialTitles"]));
foreach (JProperty p in titles) {
element.OfficialTitles.Add(p.Path, p.Value.ToString());
}
var authTowns = token.OfType<JToken>().Where(t => t.Path.StartsWith(map["AuthTowns"]));
foreach (JProperty p in authTowns) {
element.AuthTowns.Add(p.Path, p.Value.ToString());
}
return element;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> throw new NotImplementedException();
Dictionary<string, string> map = new Dictionary<string, string>() {
["Descriptions"] = "descriptions_",
["OfficialTitles"] = "official_title_",
["AuthTowns"] = "auth_town_"
};
}
Yet another solution could be to use several LINQ2JSON queries.
For instance, if you want to get all description_xyz collections in a single list
then you can do that like this:
var semiParsedJson = JObject.Parse(json);
var descriptionProperties = semiParsedJson.Properties()
.Where(prop => prop.Name.StartsWith("descriptions_"));
var collectionOfDescriptions =
from desc in descriptionProperties.Values()
select ((JArray)desc).ToObject<IEnumerable<string>>();
var flattenedDescriptions = collectionOfDescriptions.
SelectMany(desc => desc)
.ToList();
We can semi parse the json by using JObject.Parse
We can perform some filtering based on the properties' Name field
We can then get all values form each description_xyz collections as IEnumerable<string>
We will have at this point a collection of collections IEnumerable<IEnumerable<string>>, which we can flatten with SelectMany
At very end we can materialize the query with ToList
Related
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'm very new with JSON, please help!
I am trying to serialise a List<KeyValuePair<string, string>> as JSON
Currently:
[{"Key":"MyKey 1","Value":"MyValue 1"},{"Key":"MyKey 2","Value":"MyValue 2"}]
Expected:
[{"MyKey 1":"MyValue 1"},{"MyKey 2":"MyValue 2"}]
I referred to some examples from this and this.
This is my KeyValuePairJsonConverter : JsonConverter
public class KeyValuePairJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<KeyValuePair<object, object>> list = value as List<KeyValuePair<object, object>>;
writer.WriteStartArray();
foreach (var item in list)
{
writer.WriteStartObject();
writer.WritePropertyName(item.Key.ToString());
writer.WriteValue(item.Value.ToString());
writer.WriteEndObject();
}
writer.WriteEndArray();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<KeyValuePair<object, object>>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var target = Create(objectType, jsonObject);
serializer.Populate(jsonObject.CreateReader(), target);
return target;
}
private object Create(Type objectType, JObject jsonObject)
{
if (FieldExists("Key", jsonObject))
{
return jsonObject["Key"].ToString();
}
if (FieldExists("Value", jsonObject))
{
return jsonObject["Value"].ToString();
}
return null;
}
private bool FieldExists(string fieldName, JObject jsonObject)
{
return jsonObject[fieldName] != null;
}
}
I am calling it from a WebService method like this
List<KeyValuePair<string, string>> valuesList = new List<KeyValuePair<string, string>>();
Dictionary<string, string> valuesDict = SomeDictionaryMethod();
foreach(KeyValuePair<string, string> keyValue in valuesDict)
{
valuesList.Add(keyValue);
}
JsonSerializerSettings jsonSettings = new JsonSerializerSettings { Converters = new [] {new KeyValuePairJsonConverter()} };
string valuesJson = JsonConvert.SerializeObject(valuesList, jsonSettings);
You can use Newtonsoft and dictionary:
var dict = new Dictionary<int, string>();
dict.Add(1, "one");
dict.Add(2, "two");
var output = Newtonsoft.Json.JsonConvert.SerializeObject(dict);
The output is :
{"1":"one","2":"two"}
Edit
Thanks to #Sergey Berezovskiy for the information.
You are currently using Newtonsoft, so just change your List<KeyValuePair<object, object>> to Dictionary<object,object> and use the serialize and deserialize method from the package.
So I didn't want to use anything but native c# to solve a similar issue and for reference this was using .net 4, jquery 3.2.1 and backbone 1.2.0.
My issues was that the List<KeyValuePair<...>> would process out of the controller into a backbone model but when I saved that model the controller could not bind List.
public class SomeModel {
List<KeyValuePair<int, String>> SomeList { get; set; }
}
[HttpGet]
SomeControllerMethod() {
SomeModel someModel = new SomeModel();
someModel.SomeList = GetListSortedAlphabetically();
return this.Json(someModel, JsonBehavior.AllowGet);
}
network capture:
"SomeList":[{"Key":13,"Value":"aaab"},{"Key":248,"Value":"aaac"}]
But even though this set SomeList properly in the backing model.js trying to save the model without any changes to it would cause the binding SomeModel object to have the same length as the parameters in the request body but all the keys and values were null:
[HttpPut]
SomeControllerMethod([FromBody] SomeModel){
SomeModel.SomeList; // Count = 2, all keys and values null.
}
The only things I could find is that KeyValuePair is a structure and not something that can be instantiated in this manner. What I ended up doing is the following:
Add a Model wrapper somewhere that contains key, value fields:
public class KeyValuePairWrapper {
public int Key { get; set; }
public String Value { get; set; }
//default constructor will be required for binding, the Web.MVC binder will invoke this and set the Key and Value accordingly.
public KeyValuePairWrapper() { }
//a convenience method which allows you to set the values while sorting
public KeyValuePairWrapper(int key, String value)
{
Key = key;
Value = value;
}
}
Set up your binding class model to accept your custom wrapper object.
public class SomeModel
{
public List<KeyValuePairWrapper> KeyValuePairList{ get; set };
}
Get some json data out of a controller
[HttpGet]
SomeControllerMethod() {
SomeModel someModel = new SomeModel();
someModel.KeyValuePairList = GetListSortedAlphabetically();
return this.Json(someModel, JsonBehavior.AllowGet);
}
Do something at a later time, maybe model.save(null, ...) is invoked
[HttpPut]
SomeControllerMethod([FromBody] SomeModel){
SomeModel.KeyValuePairList ; // Count = 2, all keys and values are correct.
}
I get a json string which have few data uniformity issue.
For example one field in json string returns a list of string while the same field in other json string returns a dictionary(key, value pairs).
My class which holds the parsed json values have property for the field as List.
Because of this data uniformity problem, the json string is not parsed properly.
Following is my code to parse the json string
JavaScriptSerializer serializer = new JavaScriptSerializer();
myClass mc = serializer.Deserialize<myClass>(jsonString);
IS there any way with which i can write custom code to parse the json string and map it to myClass?
You don't give a concrete example of what you are trying to accomplish, which means we need to make up an example ourselves. Consider the following class:
public class myClass
{
public Dictionary<string, string> data { get; set; }
}
And consider the following two JSON strings:
{"data": ["zero", 1, "two"]}
{"data": {"0": "zero", "1":1, "2":"two"}}
It seems like you might like to parse these identically, with the array being converted to a Dictionary<string, string> whose keys are array indices. This can be accomplished with the following JavaScriptConverter:
public class myClassConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var myClass = new myClass();
object data;
if (dictionary.TryGetValue("data", out data))
{
if (data.IsJsonArray())
{
myClass.data = data.AsJsonArray()
.Select((o, i) => new KeyValuePair<int, object>(i, o))
.ToDictionary(p => p.Key.ToString(NumberFormatInfo.InvariantInfo), p => serializer.ConvertToType<string>(p.Value));
}
else if (data.IsJsonObject())
{
myClass.data = data.AsJsonObject()
.ToDictionary(p => p.Key, p => serializer.ConvertToType<string>(p.Value));
}
}
return myClass;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get { return new [] { typeof(myClass) }; }
}
}
public static class JavaScriptSerializerObjectExtensions
{
public static bool IsJsonArray(this object obj)
{
if (obj is string || obj.IsJsonObject())
return false;
return obj is IEnumerable;
}
public static IEnumerable<object> AsJsonArray(this object obj)
{
return (obj as IEnumerable).Cast<object>();
}
public static bool IsJsonObject(this object obj)
{
return obj is IDictionary<string, object>;
}
public static IDictionary<string, object> AsJsonObject(this object obj)
{
return obj as IDictionary<string, object>;
}
}
The IDictionary<string, object> passed to Deserialize() corresponds to the key/value pairs in the JSON object being converted. For a particular key ("data" in this case) the object value will be an IDictionary<string, object> if the value is, in turn, a JSON object, and an IEnumerable (specifically an ArrayList) if the value is a JSON array. By testing the value against the appropriate type, a conversion can be made.
The converter only does deserialization. Use it like so:
var jsonString1 = #"{""data"": [""zero"", 1, ""two""]}";
var jsonString2 = #"{""data"": {""0"": ""zero"", ""1"":1, ""2"":""two""}}";
var deserializer = new JavaScriptSerializer();
deserializer.RegisterConverters(new JavaScriptConverter[] { new myClassConverter() });
var newJson1 = new JavaScriptSerializer().Serialize(deserializer.Deserialize<myClass>(jsonString1));
var newJson2 = new JavaScriptSerializer().Serialize(deserializer.Deserialize<myClass>(jsonString2));
Console.WriteLine(newJson1); // Prints {"data":{"0":"zero","1":"1","2":"two"}}
Console.WriteLine(newJson2); // Prints {"data":{"0":"zero","1":"1","2":"two"}}
Debug.Assert(newJson1 == newJson2); // No assert
In reference to this question:
How can I change property names when serializing with Json.net?
Sure, great, but can I have the cake and eat it?
What I'm looking for is an eye pleasing way have an alternate name for a property in such a way that the string may contain either.
Something like:
[BetterJsonProperty(PropertyName = "foo_bar")]
public string FooBar { get; set; }
Both
{
"FooBar": "yup"
}
and
{
"foo_bar":"uhuh"
}
would deserialize as expected.
As solution with no attribute would work or an attribute on the class like:
[AllowCStylePropertyNameAlternatives]
One way to accomplish this is to create a custom JsonConverter. The idea is to have the converter enumerate the JSON property names for objects we are interested in, strip the non-alphanumeric characters from the names and then try to match them up with the actual object properties via reflection. Here is how it might look in code:
public class LaxPropertyNameMatchingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsClass;
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
PropertyInfo[] props = objectType.GetProperties();
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
string name = Regex.Replace(jp.Name, "[^A-Za-z0-9]+", "");
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && string.Equals(pi.Name, name, StringComparison.OrdinalIgnoreCase));
if (prop != null)
prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the custom converter with a particular class, you can decorate that class with a [JsonConverter] attribute like this:
[JsonConverter(typeof(LaxPropertyNameMatchingConverter))]
public class MyClass
{
public string MyProperty { get; set; }
public string MyOtherProperty { get; set; }
}
Here is a simple demo of the converter in action:
class Program
{
static void Main(string[] args)
{
string json = #"
[
{
""my property"" : ""foo"",
""my-other-property"" : ""bar"",
},
{
""(myProperty)"" : ""baz"",
""myOtherProperty"" : ""quux""
},
{
""MyProperty"" : ""fizz"",
""MY_OTHER_PROPERTY"" : ""bang""
}
]";
List<MyClass> list = JsonConvert.DeserializeObject<List<MyClass>>(json);
foreach (MyClass mc in list)
{
Console.WriteLine(mc.MyProperty);
Console.WriteLine(mc.MyOtherProperty);
}
}
}
Output:
foo
bar
baz
quux
fizz
bang
While this solution should do the job in most cases, there is an even simpler solution if you are OK with the idea of changing the Json.Net source code directly. It turns out you can accomplish the same thing by adding just one line of code to the Newtonsoft.Json.Serialization.JsonPropertyCollection class. In this class, there is a method called GetClosestMatchProperty() which looks like this:
public JsonProperty GetClosestMatchProperty(string propertyName)
{
JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
if (property == null)
property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);
return property;
}
At the point where this method is called by the deserializer, the JsonPropertyCollection contains all the properties from the class being deserialized, and the propertyName parameter contains the name of the JSON property name being matched. As you can see, the method first tries an exact name match, then it tries a case-insensitive match. So we already have a many-to-one mapping being done between the JSON and class property names.
If you modify this method to strip out all non-alphanumeric characters from the property name prior to matching it, then you can get the behavior you desire, without any special converters or attributes needed. Here is the modified code:
public JsonProperty GetClosestMatchProperty(string propertyName)
{
propertyName = Regex.Replace(propertyName, "[^A-Za-z0-9]+", "");
JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
if (property == null)
property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);
return property;
}
Of course, modifying the source code has its problems as well, but I figured it was worth a mention.
Another way of accomplishing this is intercepting the serialization/deserialization process early, by doing some overrides the JsonReader and JsonWriter
public class CustomJsonWriter : JsonTextWriter
{
private readonly Dictionary<string, string> _backwardMappings;
public CustomJsonWriter(TextWriter writer, Dictionary<string, string> backwardMappings)
: base(writer)
{
_backwardMappings = backwardMappings;
}
public override void WritePropertyName(string name)
{
base.WritePropertyName(_backwardMappings[name]);
}
}
public class CustomJsonReader : JsonTextReader
{
private readonly Dictionary<string, string> _forwardMappings;
public CustomJsonReader(TextReader reader, Dictionary<string, string> forwardMappings )
: base(reader)
{
_forwardMappings = forwardMappings;
}
public override object Value
{
get
{
if (TokenType != JsonToken.PropertyName)
return base.Value;
return _forwardMappings[base.Value.ToString()];
}
}
}
After doing this, you can serialize by doing
var mappings = new Dictionary<string, string>
{
{"Property1", "Equivalent1"},
{"Property2", "Equivalent2"},
};
var builder = new StringBuilder();
JsonSerializer.Create().Serialize(new CustomJsonWriter(new StringWriter(builder), mappings), your_object);
and deserialize by doing
var mappings = new Dictionary<string, string>
{
{"Equivalent1", "Property1"},
{"Equivalent2", "Property2"},
};
var txtReader = new CustomJsonReader(new StringReader(jsonString), mappings);
var your_object = JsonSerializer.Create().Deserialize<Your_Type>(txtReader);
I have a little test class like so :
public class Command
{
public dynamic MyData { get; set; }
}
As the dynamic MyData I want to use ExpandoObject, so I can do:
Command cmd = new Command();
cmd.MyData = new ExpandoObject();
cmd.MyData.SomeStuff = 4;
cmd.MyData.SomeOtherStuff = "hi";
I am trying to serialize to/deserialize from json. To do this I am using JavaScriptSerializer.
I want an example object above to serialize to:
{
MyData : {
SomeStuff : 4,
SomeOtherStuff : "hi"
}
}
To do this I need a JavaScriptConverter (taken from this website):
public class ExpandoJsonConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
return dictionary.ToExpando();
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var result = new Dictionary<string, object>();
var dictionary = obj as IDictionary<string, object>;
foreach (var item in dictionary)
result.Add(item.Key, item.Value);
return result;
}
public override IEnumerable<Type> SupportedTypes
{
get
{
return new ReadOnlyCollection<Type>(new Type[] { typeof(ExpandoObject) });
}
}
}
public static class IDictionaryExtensions {
/// <summary>
/// Extension method that turns a dictionary of string and object to an ExpandoObject
/// Snagged from http://theburningmonk.com/2011/05/idictionarystring-object-to-expandoobject-extension-method/
/// </summary>
public static ExpandoObject ToExpando(this IDictionary<string, object> dictionary) {
var expando = new ExpandoObject();
var expandoDic = (IDictionary<string, object>)expando;
// go through the items in the dictionary and copy over the key value pairs)
foreach (var kvp in dictionary) {
// if the value can also be turned into an ExpandoObject, then do it!
if (kvp.Value is IDictionary<string, object>) {
var expandoValue = ((IDictionary<string, object>)kvp.Value).ToExpando();
expandoDic.Add(kvp.Key, expandoValue);
}
else if (kvp.Value is ICollection) {
// iterate through the collection and convert any strin-object dictionaries
// along the way into expando objects
var itemList = new List<object>();
foreach (var item in (ICollection)kvp.Value) {
if (item is IDictionary<string, object>) {
var expandoItem = ((IDictionary<string, object>)item).ToExpando();
itemList.Add(expandoItem);
}
else {
itemList.Add(item);
}
}
expandoDic.Add(kvp.Key, itemList);
}
else {
expandoDic.Add(kvp);
}
}
return expando;
}
}
Now this works neat for serializing, but there is a following problem with deserializing:
since MyData is a dynamic object, and the ExpandoJsonConverter expects ExpandoObject, the data deserialized to MyData is of type IDictionary<string, object>.
if I change dynamic MyData to be ExpandoObject MyData, I won't be able to say cmd.MyData.SomeStuff = 4;, the compiler will tell me that "ExpandoObject does not have property named SomeStuff".
finally, I could add dynamic to the list of supported types of ExpandoJsonConverter, byt wait, you cant do typeof(dynamic).
Is anyone aware of a neat workaround? I would really like this functionality, but I can't use 3rd party serialization libraries like Newtonsoft. Thanks.
Deserialize to ExpandoObject but declare the variable dynamic, i.e. what you need is dynamic d = js.Deserialize<ExpandoObject>(json):
string json = #"{
MyData : {
SomeStuff : 4,
SomeOtherStuff : ""hi""
}
}";
var js = new JavaScriptSerializer();
js.RegisterConverters(new[] { new ExpandoJsonConverter() });
dynamic d = js.Deserialize<ExpandoObject>(json);
Console.WriteLine(d.MyData.SomeOtherStuff);
Output:
hi
If you need a Command object, just construct one manually and inject the dynamic object returned from the serializer:
var cmd = new Command { MyData = d };
Console.WriteLine(cmd.MyData.SomeStuff);