I'm working with C# MongoDB driver and I have this rather complex JSON struct to save:
{
"name" : "value",
"age": 1,
"isFemale": true,
"Hobbies" : {
//All data within the "Hobbies" node is dynamic
//and may change from one item to another.
"stringItem" : "value",
"intItem" : 0.0,
"listOfItems" : [
{ "field" : 1696.0 }
],
"intArray" : [ 566.0, 1200.0 ]
},
"Collection" : [
//All data within the "Collection" node is dynamic
//and may change from one item to another.
{
"field" : "string",
"FieldTypeId" : 2.0,
"array" : [
{ "value" : "1024x1000" }
]
}
]
}
I have this class that represents the above object:
public class MyClass
{
public string Name;
public int Age;
public bool IsFemale;
public Dictionary<string, object> Hobbies;
public List<Dictionary<string, object>> Collection;
}
Here is what I get saved for the "Hobbies" section:
"Hobbies": {
"stringItem": "value",
"intItem": 1,
"listOfItems": {
"_t": "Newtonsoft.Json.Linq.JArray, Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed",
"_v": [{
"_t": "JObject",
"_v": [{
"_t": "JProperty",
"_v": [{
"_t": "JValue",
"_v": []
}]
},
{
"_t": "JProperty",
"_v": [{
"_t": "JValue",
"_v": []
}]
}]
}]
}
}
My guess is that I should write a custom serializer for that class but I couldn't find any useful example in the MONGODB .NET DRIVER Manual
I tried to start and implement something like this:
public class MyClassSerializer : SerializerBase<MyClass>, IBsonDocumentSerializer
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, MyClass value)
{
//What to do here???
base.Serialize(context, args, value);
}
public override MyClass Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
//What to do here???
return base.Deserialize(context, args);
}
public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo)
{
switch (memberName)
{
case "Name":
serializationInfo = new BsonSerializationInfo(memberName, new StringSerializer(), typeof(string));
return true;
case "Age":
serializationInfo = new BsonSerializationInfo(memberName, new Int16Serializer(), typeof(string));
return true;
case "IsFemale":
serializationInfo = new BsonSerializationInfo(memberName, new BooleanSerializer(), typeof(string));
return true;
case "Hobbies":
serializationInfo = new BsonSerializationInfo(memberName, new TupleSerializer<string, object>(), typeof(string));
return true;
case "Collection":
serializationInfo = new BsonSerializationInfo(memberName, new ArraySerializer<Dictionary<string, object>>(), typeof(string));
return true;
default:
serializationInfo = null;
return false;
}
}
}
But I couldn't find anything to do with this. It is not fully implemented and I read that I need to register the serializer somewhere, but where...?
A simple way for dealing with this issue and not have to create additional properties, is registering a custom serializer.
ComplexTypeSerializer.cs
namespace MyNamespace.MongoDB.Serializers
{
public class ComplexTypeSerializer : SerializerBase<object>
{
public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var serializer = BsonSerializer.LookupSerializer(typeof(BsonDocument));
var document = serializer.Deserialize(context, args);
var bsonDocument = document.ToBsonDocument();
var result = BsonExtensionMethods.ToJson(bsonDocument);
return JsonConvert.DeserializeObject<IDictionary<string, object>>(result);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
var jsonDocument = JsonConvert.SerializeObject(value);
var bsonDocument = BsonSerializer.Deserialize<BsonDocument>(jsonDocument);
var serializer = BsonSerializer.LookupSerializer(typeof(BsonDocument));
serializer.Serialize(context, bsonDocument.AsBsonValue);
}
}
}
And register it as per http://mongodb.github.io/mongo-csharp-driver/2.3/reference/bson/serialization/
BsonSerializer.RegisterSerializer(typeof(IDictionary<string, object>), new ComplexTypeSerializer());
or
BsonSerializer.RegisterSerializer(typeof(IList<IDictionary<string, object>>), new ComplexTypeSerializer());
The code below was tested with a simple IDictionary<string, object>, not sure if it would work with IList<IDictionary<string, object>>, however if it's not supported, you can create another custom serializer to support it.
Found how to save the data to MongoDB here: Dictionary-to-BsonDocument conversion omitting _t field and extended it a bit so I thought to share the full solution.
Step #1:
In my class, I declared 2 members for each value:
// For the Hobbies object type:
[BsonIgnore] //ignore this value in MongoDB
public Dictionary<string, object> Hobbies { get; set; }
[JsonIgnore] //ignore this value in the response on Get requests
[BsonElement(elementName: "Hobbies")]
public BsonDocument HobbiesBson { get; set; }
/*********************************************************************/
// For the Collection object type:
[BsonIgnore] //ignore this value in MongoDB
public List<Dictionary<string, object>> Collection { get; set; }
[JsonIgnore] //ignore this value in the response on Get requests
[BsonElement(elementName: "Collection")]
public BsonArray CollectionBson { get; set; }
Step #2
In my WebAPI controller method for Post
[HttpPost]
public override async Task<IActionResult> Post([FromBody] Person person)
{
var jsonDoc = JsonConvert.SerializeObject(person.Hobbies);
person.HobbiesBson = BsonSerializer.Deserialize<BsonDocument>(jsonDoc);
jsonDoc = JsonConvert.SerializeObject(person.Collection);
person.CollectionBson = BsonSerializer.Deserialize<BsonArray>(jsonDoc);
//save
}
Step #3
In my Get request I deserialize it back like this:
[HttpGet("{id?}")]
public override async Task<IActionResult> Get(string id = null)
{
var people = //get data from mongoDB
foreach (var person in people)
{
var bsonDoc = BsonExtensionMethods.ToJson(person.HobbiesBson);
person.Hobbies = JsonConvert.DeserializeObject<Dictionary<string, object>>(bsonDoc);
bsonDoc = BsonExtensionMethods.ToJson(person.CollectionBson);
person.Collection = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(bsonDoc);bsonDoc);
}
return Ok(people);
}
This solved my issue and I hope it can help others as well :-)
Related
I have JSON data that I need to parse from C# object.
this is JSON Example.
{
"types":
[
[
"tour_type",
[
["groups",1],
["individual",2]
]
]
]
}
Here are my C# classes that are meant to contain that data:
using System;
using Newtonsoft.Json;
namespace JsonDeserializationTest
{
[JsonProperty("types")]
public class Types
{
[JsonProperty]
public List<Type> Values {get;set;}
}
public class Type
{
[JsonProperty]
public string Key {get;set;}
[JsonProperty]
public List<Dictionary<string, int>> Values { get; set; }
}
}
It's not working now.
How can I fix it?
Use the JsonSerializer (System.Text.Json) object.
Code:
YourClass obj = JsonSerializer.Deserialize<YourClass>(jsonString);
Your json has a list of list of the object... but you are declaring only List of the object.
public class Types
{
[JsonProperty("types")]
public List<List<object>> Values { get; set; }
// ------ UPDATE: This can only be list of list of 'object' ------- \\
}
Also, you are using the JsonProperty on the class, which is not where that normally goes. You want to use that on the property of the class.
UPDATE:
You cannot use List<List<Type>> for the json you are getting, it can only be List<List<object>>. You have to use object because it can either be a string or a List<List<string>>. After you update your Types class, you can successfully deserialize the json above.
var obj = JsonConvert.DeserializeObject<Types>(json);
and based on your json definition, you can access tour_type by using the following code
types.Values.First()[0].ToString()
// output: tour_type
List<List<string>> data = JsonConvert.DeserializeObject<List<List<string>>>(types.Values.First()[1].ToString())
// data[0]
[0]: "groups"
[1]: "1"
// data[1]
[0]: "individual"
[1]: "2"
Since both of the items in the types are objects, you will either have to convert them to string or a list of list of strings or whatever object they actually are.
The JSON payload in the provided example is formatted quite strangely, especially since it contains seemingly unnecessary array nesting. A payload like this usually includes more nested objects (rather than a bunch of nested arrays). Additionally, it has a list of (string, int) pairs, which is semantically very similar to a Dictionary<string, int>, but the payload doesn't lend itself to that. It would be helpful to know where it is coming from (what context) to understand how it might change.
The example JSON brings up a few questions (that you may want to ask yourself):
Can the "types" array contain multiple entries (at its immediate nesting)?
Can the "tour_type" key name appear after the array of string, int pairs? Is it possible for an entry where no such name exists?
What other elements can exist in the arrays within "tour_type"?
Is it guaranteed that the most nested array will contain just a single (string, int) pair?
Similarly, it is hard to understand what the example C# class is trying to encapsulate. Is List<Dictionary<string, int>> necessary?
All that said, here's a solution using the built-in System.Text.Json library, that could work for you. You could write something similar using Newtonsoft.Json, if necessary. The solution assumes:
We can't change the JSON payload (and that the third party API response will always returns something that is structurally similar to the example)
We can only make minimal changes to the C# class object provided in the example
The solution creates and a JsonConverter<T> that uses the low-level Utf8JsonReader to manually parse and create the custom object. This is required since nested "[" are being used to delineate what should be objects rather than "{". The converter is then registered by annotating the class with the attribute. Now, simply call JsonSerializer.Deserialize, passing in the JSON payload.
public class Tours
{
[JsonPropertyName("types")]
public List<UserType> Types { get; set; }
}
// Annotate the type to register the converter to use
[JsonConverter(typeof(CustomUserTypeConverter))]
public class UserType
{
public string Key { get; set; }
public Dictionary<string, int> Values { get; set; }
}
// This will use the low-level reader to build up the UserType
public class CustomUserTypeConverter : JsonConverter<UserType>
{
// Extra structural validation was done for invalid/incomplete JSON
// which might be too strict or incorrect and hence might require adjustments.
public override UserType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var result = new UserType();
if (!reader.Read())
{
throw new JsonException("Incomplete JSON.");
}
if (reader.TokenType != JsonTokenType.EndArray)
{
result.Key = reader.GetString();
ReadAndValidate(ref reader, JsonTokenType.StartArray);
int depthSnapshot = reader.CurrentDepth;
var values = new Dictionary<string, int>();
do
{
reader.Read();
if (reader.TokenType != JsonTokenType.StartArray && reader.TokenType != JsonTokenType.EndArray)
{
throw new JsonException($"Invalid JSON payload. Expected Start or End Array. TokenType: {reader.TokenType}, Depth: {reader.CurrentDepth}.");
}
if (reader.CurrentDepth <= depthSnapshot)
{
break;
}
reader.Read();
if (reader.TokenType != JsonTokenType.EndArray)
{
string key = reader.GetString();
reader.Read();
int value = reader.GetInt32();
values.Add(key, value);
ReadAndValidate(ref reader, JsonTokenType.EndArray);
}
} while (true);
ReadAndValidate(ref reader, JsonTokenType.EndArray);
result.Values = values;
}
return result;
}
private void ReadAndValidate(ref Utf8JsonReader reader, JsonTokenType expectedTokenType)
{
bool readNext = reader.Read();
if (!readNext || reader.TokenType != expectedTokenType)
{
string message = readNext ?
$"Invalid JSON payload. TokenType: {reader.TokenType}, Depth: {reader.CurrentDepth}, Expected: {expectedTokenType}" :
$"Incomplete JSON. Expected: {expectedTokenType}";
throw new JsonException(message);
}
}
// Implement this method if you need to Serialize (i.e. write) the object
// back to JSON
public override void Write(Utf8JsonWriter writer, UserType value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Here's how you would use the above converter to serialize the JSON string provided in the example, along with how to access the values.
public static Tours ParseJson(string json)
{
Tours tours = JsonSerializer.Deserialize<Tours>(json);
return tours;
}
public static void AccessValues(Tours tours)
{
foreach (UserType data in tours.Types)
{
string typeName = data.Key; // "tour_type"
foreach (KeyValuePair<string, int> pairs in data.Values)
{
string key = pairs.Key; // "groups" or "individual
int value = pairs.Value; // 1 or 2
}
}
}
For what it's worth, Visual Studio suggests the following C# class structure for the example JSON (which is similar to what #Jawad suggested):
public class Rootobject
{
public object[][] types { get; set; }
}
Hope that helps.
I couldn't figure out your JSON so I created an example with verified JSON.
Try this:
JSON:
{
"Items": [
{
"Name": "tour",
"Attributes": [
{
"Name": "groups",
"Value": 1
},
{
"Name": "individual",
"Value": 2
}
]
},
{
"Name": "demo",
"Attributes": [
{
"Name": "this is demo",
"Value": 3
},
{
"Name": "design pattern",
"Value": 99
}
]
}
]
}
Types foo = JsonSerializer.Deserialize<Types>(jsonString);
public class TypeAttribute
{
public string Name { get; set; }
public int Value { get; set; }
}
public class Type
{
private readonly ICollection<TypeAttribute> _attributes;
public Type()
{
_attributes = new Collection<TypeAttribute>();
}
public void AddAttributes(IEnumerable<TypeAttribute> attrs)
{
foreach(TypeAttribute ta in attrs)
{
_attributes.Add(ta);
}
}
public string Name { get; set; }
public IEnumerable<TypeAttribute> Attributes
{
get { return _attributes; }
set
{
foreach(TypeAttribute ta in value)
{
_attributes.Add(ta);
}
}
}
}
public class Types
{
ICollection<Type> _items;
public Types()
{
_items = new Collection<Type>();
}
public void AddItems(IEnumerable<Type> tps)
{
foreach (Type t in tps)
{
_items.Add(t);
}
}
public IEnumerable<Type> Items
{
get { return _items; }
set
{
foreach (Type t in value)
{
_items.Add(t);
}
}
}
}
How to fill ObservableCollection using JSON? Now there is only the script itself and the model in the desktop application. I can not understand how to tie it up.
I get it after running the script:
{
"records": [
{
"brand_id": "1",
"brand_name": "Gigabyte"
},
{
"brand_id": "2",
"brand_name": "MSI"
},
{
"brand_id": "3",
"brand_name": "Lenovo"
},
{
"brand_id": "4",
"brand_name": "Dell"
},
{
"brand_id": "5",
"brand_name": "Google"
}
]}
And I have a Model in app:
public class Brands
{
int brand_id;
string brand_name;
public int Brand_id { get => brand_id; set => brand_id = value; }
public string Brand_name { get => brand_name; set => brand_name = value; }
}
And Collection:
public class BrandsCollection
{
private ObservableCollection<Brands> brands;
public ObservableCollection<Brands> Brands { get => brands; set => brands = value; }
}
It is fairly straight forward, especially with packages available to simplify a lot of the work. The Nuget Package System.Net.Http will have the packages you need to create an HttpClient to Get() your JSON from the web. I recommend Newtonsoft.Json to parse JSON into a C# object. And then having the object you just need to set the DataGrid.ItemSource to be an array of objects of any type for it to generate columns.
A simple example:
First you would define a simple object representation of your JSON data.
For instance if you had the following data:
[
{
"Name":"Test",
"Data": ["Item1","Item2"]
},
{
"Name":"Test 2",
"Data": ["Item3","Item4"]
}
]
You would have to create an equivalent C# representation.
Basically this is a List of objects so:
public class OuterObject : List<InnerObject> {}
The inner object is as follows:
public class InnerObject {
public string Name { get; set; }
public List<string> Data { get; set; }
}
Having the objects defined you can do something like:
HttpClient client = new HttpClient { BaseAddress = new Uri("ADDRESS") };
var json = await client.GetAsync("/ENDPOINT");
JsonSerializer serializer = JsonSerializer.CreateDefault();
using (StringReader reader = new StringReader(json))
{
using (JsonTextReader jsonReader = new JsonTextReader(reader))
{
var result = serializer.Deserialize<OuterObject>(jsonReader);
}
}
Then to access the data in your program you can access it like so:
string name = result[0].Name;
Or set it to a DataGrid's ItemSource to have the Data show up magically.
grid1.ItemSource = result;
As long as the result is an array of items it will create a row per item.
You may want to specify which items are shown but that is done by modifying DataGrid.Columns definitions and setting DataGrid.AutogenerateColumns = false
EDIT: With your data and models
//Just a small change to the Collection
public class BrandsCollection {
private ObservableCollection<Brands> _records;
public ObservableCollection<Brands> records { get => _records; set => _records= value; }
}
And to parse the data...
JsonSerializer serializer = JsonSerializer.CreateDefault();
using (StringReader reader = new StringReader(json))
{
using (JsonTextReader jsonReader = new JsonTextReader(reader))
{
var result = serializer.Deserialize<BrandsCollection>(jsonReader);
}
}
You have to remember to either use the same names as the json labels or use Json Attributes. For more info on that you can go to the official Newtonsoft.Json documentation
Given following json result:
The default json result has a known set of fields:
{
"id": "7908",
"name": "product name"
}
But can be extended with additional fields (in this example _unknown_field_name_1 and _unknown_field_name_2) of which the names are not known when requesting the result.
{
"id": "7908",
"name": "product name",
"_unknown_field_name_1": "some value",
"_unknown_field_name_2": "some value"
}
I would like the json result to be serialized and deserialized to and from a class with properties for the known fields and map the unknown fields (for which there are no properties) to a property (or multiple properties) like a dictionary so they can be accessed and modified.
public class Product
{
public string id { get; set; }
public string name { get; set; }
public Dictionary<string, string> fields { get; set; }
}
I think I need a way to plug into a json serializer and do the mapping for the missing members myself (both for serialize and deserialize).
I have been looking at various possibilities:
json.net and custom contract resolvers (can't figure out how to do it)
datacontract serializer (can only override onserialized, onserializing)
serialize to dynamic and do custom mapping (this might work, but seems a lot of work)
let product inheriting from DynamicObject (serializers work with reflection and do not invoke the trygetmember and trysetmember methods)
I'm using restsharp, but any serializer can be plugged in.
Oh, and I cannot change the json result, and this or this didn't help me either.
Update:
This looks more like it: http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx
An even easier option to tackling this problem would be to use the JsonExtensionDataAttribute from JSON .NET
public class MyClass
{
// known field
public decimal TaxRate { get; set; }
// extra fields
[JsonExtensionData]
private IDictionary<string, JToken> _extraStuff;
}
There's a sample of this on the project blog here
UPDATE Please note this requires JSON .NET v5 release 5 and above
See https://gist.github.com/LodewijkSioen/5101814
What you were looking for was a custom JsonConverter
This is a way you could solve it, although I don't like it that much. I solved it using Newton/JSON.Net. I suppose you could use the JsonConverter for deserialization aswell.
private const string Json = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}";
[TestMethod]
public void TestDeserializeUnknownMembers()
{
var #object = JObject.Parse(Json);
var serializer = new Newtonsoft.Json.JsonSerializer();
serializer.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Error;
serializer.Error += (sender, eventArgs) =>
{
var contract = eventArgs.CurrentObject as Contract ?? new Contract();
contract.UnknownValues.Add(eventArgs.ErrorContext.Member.ToString(), #object[eventArgs.ErrorContext.Member.ToString()].Value<string>());
eventArgs.ErrorContext.Handled = true;
};
using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(Json)))
using (StreamReader streamReader = new StreamReader(memoryStream))
using (JsonReader jsonReader = new JsonTextReader(streamReader))
{
var result = serializer.Deserialize<Contract>(jsonReader);
Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_1"));
Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_2"));
}
}
[TestMethod]
public void TestSerializeUnknownMembers()
{
var deserializedObject = new Contract
{
id = 7908,
name = "product name",
UnknownValues = new Dictionary<string, string>
{
{"_unknown_field_name_1", "some value"},
{"_unknown_field_name_2", "some value"}
}
};
var json = JsonConvert.SerializeObject(deserializedObject, new DictionaryConverter());
Console.WriteLine(Json);
Console.WriteLine(json);
Assert.AreEqual(Json, json);
}
}
class DictionaryConverter : JsonConverter
{
public DictionaryConverter()
{
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Contract);
}
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 contract = value as Contract;
var json = JsonConvert.SerializeObject(value);
var dictArray = String.Join(",", contract.UnknownValues.Select(pair => "\"" + pair.Key + "\":\"" + pair.Value + "\""));
json = json.Substring(0, json.Length - 1) + "," + dictArray + "}";
writer.WriteRaw(json);
}
}
class Contract
{
public Contract()
{
UnknownValues = new Dictionary<string, string>();
}
public int id { get; set; }
public string name { get; set; }
[JsonIgnore]
public Dictionary<string, string> UnknownValues { get; set; }
}
}
I thought I'd throw my hat in the ring since I had a similar problem recently. Here's an example of the JSON I wanted to deserialize:
{
"agencyId": "agency1",
"overrides": {
"assumption.discount.rates": "value: 0.07",
".plan": {
"plan1": {
"assumption.payroll.growth": "value: 0.03",
"provision.eeContrib.rate": "value: 0.35"
},
"plan2": {
".classAndTier": {
"misc:tier1": {
"provision.eeContrib.rate": "value: 0.4"
},
"misc:tier2": {
"provision.eeContrib.rate": "value: 0.375"
}
}
}
}
}
}
This is for a system where overrides apply at different levels and are inherited down the tree. In any case, the data model I wanted was something that would allow me to have a property bag with these special inheritance rules also supplied.
What I ended up with was the following:
public class TestDataModel
{
public string AgencyId;
public int Years;
public PropertyBagModel Overrides;
}
public class ParticipantFilterModel
{
public string[] ClassAndTier;
public string[] BargainingUnit;
public string[] Department;
}
public class PropertyBagModel
{
[JsonExtensionData]
private readonly Dictionary<string, JToken> _extensionData = new Dictionary<string, JToken>();
[JsonIgnore]
public readonly Dictionary<string, string> Values = new Dictionary<string, string>();
[JsonProperty(".plan", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, PropertyBagModel> ByPlan;
[JsonProperty(".classAndTier", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, PropertyBagModel> ByClassAndTier;
[JsonProperty(".bargainingUnit", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, PropertyBagModel> ByBarginingUnit;
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
foreach (var kvp in Values)
_extensionData.Add(kvp.Key, kvp.Value);
}
[OnSerialized]
private void OnSerialized(StreamingContext context)
{
_extensionData.Clear();
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
Values.Clear();
foreach (var kvp in _extensionData.Where(x => x.Value.Type == JTokenType.String))
Values.Add(kvp.Key, kvp.Value.Value<string>());
_extensionData.Clear();
}
}
The basic idea is this:
The PropertyBagModel on deserialization by JSON.NET has the ByPlan, ByClassAndTier, etc. fields populated and also has the private _extensionData field populated.
Then JSON.NET calls the private OnDeserialized() method and that will move the data from _extensionData to Values as appropriate (or drop it on the floor otherwise - presumably you could log this if it was something you wanted to know). We then remove the extra gunk from _extensionData so it doesn't consume memory.
On serialization, the OnSerializing method gets calls where we move stuff into _extensionData so it gets saved.
When serialization has finished, OnSerialized gets called and we remove the extra stuff from _extensionData.
We could further delete and recreate the _extensionData Dictionary when needed but I didn't see a real value in this as I'm not using tons of these objects. To do this we'd just create on OnSerializing and delete on OnSerialized. On OnDeserializing, instead of clearing, we could free it.
I was looking into a similar issue and found this post.
Here is a way to do it using reflection.
To make it more generic, one should check the type of the property instead of simply using ToString() in propertyInfo.SetValue, unless OFC all the actual properties are strings.
Also, lowercase property names is not standard in C# but given that GetProperty is case sensitive there are few other options.
public class Product
{
private Type _type;
public Product()
{
fields = new Dictionary<string, object>();
_type = GetType();
}
public string id { get; set; }
public string name { get; set; }
public Dictionary<string, object> fields { get; set; }
public void SetProperty(string key, object value)
{
var propertyInfo = _type.GetProperty(key);
if (null == propertyInfo)
{
fields.Add(key,value);
return;
}
propertyInfo.SetValue(this, value.ToString());
}
}
...
private const string JsonTest = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}";
var product = new Product();
var data = JObject.Parse(JsonTest);
foreach (var item in data)
{
product.SetProperty(item.Key, item.Value);
}
I send requests to API and have two responses.
First:
{"response": [{...}, {...}]}
Second:
{"response": {"count": 0, "items": [{...}, {...}]}}
Can I create one class for two cases? I use C# and Json.NET.
Yes, you can use a custom JsonConverter detect the JSON format and deserialize into the same class(es). Below is a converter that can handle both formats. It assumes that you are going to deserialize into a List<Item>.
class ItemListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<Item>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken response = JToken.Load(reader)["response"];
if (response.Type == JTokenType.Array)
{
return response.ToObject<List<Item>>();
}
else
{
return response["items"].ToObject<List<Item>>();
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a demo showing how to use the converter:
class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
string json1 = #"
{
""response"" :
[
{ ""Id"" : 1, ""Name"" : ""Foo"" },
{ ""Id"" : 2, ""Name"" : ""Bar"" },
]
}";
DeserializeAndDump(json1);
string json2 = #"
{
""response"" :
{
""count"" : 2,
""items"" :
[
{ ""Id"" : 3, ""Name"" : ""Fizz"" },
{ ""Id"" : 4, ""Name"" : ""Buzz"" },
]
}
}";
DeserializeAndDump(json2);
}
private static void DeserializeAndDump(string json)
{
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ItemListConverter());
List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json, settings);
foreach (Item item in list)
{
Console.WriteLine("Id: " + item.Id + ", Name: " + item.Name);
}
}
}
Here is the output of the above:
Id: 1, Name: Foo
Id: 2, Name: Bar
Id: 3, Name: Fizz
Id: 4, Name: Buzz
First, if you are not aware of it, there is this online tool http://json2csharp.com/ where you can generate your C# class from your JSON string. I think it could be better to use two different classes. Is there a specific reason to use only one?
You can use JsonExtensionData attribute
Only declare the properties you’re interested in and let extension data do the rest.
public class CustomerInvoice
{
// we're only modifing the tax rate
public decimal TaxRate { get; set; }
// everything else gets stored here
[JsonExtensionData]
private IDictionary<string, JToken> _additionalData;
}
string json = #"{
'HourlyRate': 150,
'Hours': 40,
'TaxRate': 0.125
}";
var invoice = JsonConvert.DeserializeObject<CustomerInvoice>(json);
// increase tax to 15%
invoice.TaxRate = 0.15m;
string result = JsonConvert.SerializeObject(invoice);
// {
// 'TaxRate': 0.15,
// 'HourlyRate': 150,
// 'Hours': 40
// }
Using extension data to round-trip JSON like this also means you don’t
need to worry about third-party sources adding additional JSON because
it will automatically be preserved when serializing/deserializing.
Nifty.
If you don’t want extension data serialized (or deserialized) then
disable that functionality by setting WriteData and ReadData
properties on ExtensionDataAttribute to false.
Reference: Json.NET 5.0 Release 7 – Immutable Collections
or custom JsonConvert, so look here Deserialize JSON not working with JSON.NET
Json.Net typically serializes a Dictionary<k,v> into a collection;
"MyDict": {
"Apples": {
"Taste": 1341181398,
"Title": "Granny Smith",
},
"Oranges": {
"Taste": 9999999999,
"Title": "Coxes Pippin",
},
}
Which is great. And from looking around on SO it seems to be what most people want. However, in this particular case, I want to serialize between my Dictionary<k,v> and the Array format instead;
"MyDict": [
"k": "Apples",
"v": {
"Taste": 1341181398,
"Title": "Granny Smith",
}
},
"k:": "Oranges",
"v:": {
"Taste": 9999999999,
"Title": "Coxes Pippin",
}
},
]
Is there an easy way to do this with my existing field type? Is there an attribute I can annotate for instance?
Another way to accomplish this is to use a custom ContractResolver. That way you do not have to subclass Dictionary<K,V> nor apply a transform each time you serialize, as suggested in other answers.
The following resolver will cause ALL dictionaries to be serialized as an array of objects with "Key" and "Value" properties:
class DictionaryAsArrayResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (objectType.GetInterfaces().Any(i => i == typeof(IDictionary) ||
(i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))))
{
return base.CreateArrayContract(objectType);
}
return base.CreateContract(objectType);
}
}
To use the resolver, add it to your JsonSerializerSettings, then pass the settings to JsonConvert.SerializeObject() like this:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new DictionaryAsArrayResolver();
string json = JsonConvert.SerializeObject(obj, settings);
Here is a working demo.
Ah, it turns out this is as straightforward as I'd hoped. My Dictionary<k,v> is subclassed already and I found that I can annotate it with [JsonArrayAttribute]. That gives me exactly the format I need;
"MyDict": [
{
"Key": "Apples",
"Value": {
"Taste": 1341181398,
"Title": "Granny Smith",
}
},
{
"Key:": "Oranges",
"Value:": {
"Taste": 9999999999,
"Title": "Coxes Pippin",
}
},
]
For this example, I'll use the dictonary:
var myDict = new Dictionary<string,string>() {
{"a","123"},
{"b","234"},
{"c","345"}
};
which serializes (with Newtonsoft.Json.JsonConvert.SerializeObject(myDict)) to:
{"a":"123","b":"234","c":"345"}
You could do a transform using LINQ to create an anonymous object, and serialize that:
var myDictTransformed = from key in myDict.Keys
select new { k = key, v = myDict[key] };
Or you could use a real object
class MyDictEntry
{
public string k { get; set; }
public string v { get; set; }
}
and either the above or the alternative LINQ syntax:
var myDictTransformed = myDict.Keys.AsEnumerable()
.Select(key => new MyDictEntry{
k = key,
v = myDict[key]
});
Either way, this serializes to:
[
{"k":"a", "v":"123"},
{"k":"b", "v":"234"},
{"k":"c", "v":"345"}
]
.NET Fiddle link: https://dotnetfiddle.net/LhisVW
The simplest solution I found is to convert your Dictionary<string, string> to a List<KeyValuePair<string, string>>. JSON.NET then converts your List into an array of objects with the form { Key: 'keyname', Value: 'value' }. This works well if you accept the required model change and don't want to subclass your Dictionary.
gregmac's answer was helpful, but didn't quite work. The following is the same idea... without the puns.
var dictionaryTransformed = dictionary.Select(item => item.Key).Select(i =>
new {Key = i, Value = dictionary[i] });
or of course
var dictionaryTransformed = dictionary.Select(item =>
new {item.Key, Value = dictionary[item.Key] });
Then to json
var json = (new JavaScriptSerializer()).Serialize(
new { Container = dictionaryTransformed.ToArray() } )
I'm not exactly sure why, but the custom ContractResolver by Brian Rogers listed above didn't work for me. It seemed to get into an endless loop somewhere internally. Possibly due to other parts of my json.net setup.
Anyway - this workaround did the trick for me.
public interface IStrongIdentifier
{
string StringValue { get; set; }
}
public class StrongIdentifierKeyedDictionaryWrapper<TKey, TValue> : Dictionary<string, TValue>
where TKey : IStrongIdentifier
{
public void Add(TKey key, TValue value)
{
base.Add(key.StringValue, value);
}
public void Remove(TKey key)
{
base.Remove(key.StringValue);
}
public TValue this[TKey index]
{
get => base[index.StringValue];
set => base[index.StringValue] = value;
}
public bool ContainsKey(TKey key)
{
return base.ContainsKey(key.StringValue);
}
}