I have some fairly complex JSON and need to use Newtonsoft to deserialize, as System.Text.Json is not as extensible as Newtonsoft.
My problem is as follows: I have a class, let's call "A", which I use a few places in my app including saving to a DB using EFCore and converting to a binary file. I populate the class using a JSON File, but due to requirements beyond my control I need to keep the default value setter in the class. However, IF the property does NOT exist in the Json we are deserializing, I'd like to use a custom default value.
Example Class:
public class A
{
public int Id { get; set; } = 0;
public bool IsRequired { get; set; } = true;
}
And if this is my Json:
[{
"id": 4,
"isRequired": true;
},
{
"id": 7
}]
I'd like to override the isRequired default to false if the key is not in the json.
var list = JsonConvert.DeserializeObject<List<A>>( -- some settings --);
// list[0]
// - Id = 4
// - IsRequired = true
//
// list[1]
// - Id = 6
// - IsRequired = false
I've been able to get a basic version of this working as described in this SO post, but the deserialization is very simple and breaks immediately with nested properties and complex types + my custom contract resolver I have.
Note that I am using .NET 6, and my models are separated from my deserializers.
You just can add a json constructor to your class. You don't need to put all properties in the constructor, only those properties that behavier the special way
public class A
{
public int Id { get; set;} = 0;
public bool IsRequired { get; set; } = true;
[JsonConstructor]
public A (bool? isRequired)
{
this.IsRequired = isRequired ?? false;
}
}
test
var list = JsonConvert.DeserializeObject<List<A>>(json);
json=JsonConvert.SerializeObject(list, Newtonsoft.Json.Formatting.Indented);
result
[
{
"Id": 4,
"IsRequired": true
},
{
"Id": 7,
"IsRequired": false
}
]
There are two options using a JsonConverter for that
The easy way (if you may extend the existing object)
Since you already have written half of the answer within your question "I'd like to override the isRequired default to false" then lets use the intended behavior for overwriting in combination to a custom JsonConverter
First lets make the IsRequired property virtual
public class A
{
public int Id { get; set;} = 0;
public virtual bool IsRequired { get; set;} = true;
}
Afterwards we can inherit and overwrite the default behavior:
private class ADersialize : A
{
public override bool IsRequired { get; set;} = false;
}
With those modifications we can use a custom converter which uses ADeserialze:
public class AConverter : JsonConverter
{
public static readonly AConverter Instance = new AConverter();
public override bool CanConvert(Type objectType)
{
return objectType == typeof(A);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<ADersialize>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
And with those changes you can just deserialize it:
var list = JsonConvert.DeserializeObject<List<A>>(x, AConverter.Instance);
Option two is when you cannot modify A at all. Then you also need to write a custom JsonConverter and within ReadJson initialize A and set IsRequired = false and afterwards deserialze each field individually.
Related
I am using NewtonSoft.Json to read/write our data as json. One (very simplified) example of this is:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"AirportName": "KLAX",
"AirportRunway": "25L"
}
With a C# DTO class that mimicks the properties. Note that we use TypeNameHandling.
We want to change our C# class to a more complex setup:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class LandingEvent
{
public DateTime TimeOfLanding { get; set; }
public Airport Airport { get; set; }
}
which will result in, that new data will be written to JSON as:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"Airport": {
"Name": "KLAX",
"Runway": "25L"
}
}
But we still need to be able to read the old JSON data and parse into the new class structure. And this is what I currently struggle with.
I know that the way to go is probably a specialized JsonConverter. I have a couple of questions in this regard:
How do I read the $type property and instantiate the right type? (my overriden CanConvert() method is fed the name of a base-class (due to the real context being more complex than this example).
I only want to do custom read, if the property AirportName exsist. How do I fall-back to default deserialization, if this is not the case?
Edit: Some clarification is in order. If I create a custom JsonConverter, then CanConvert will receive the type EventBase, but the $type can actually contain either "MyNamespace.LandingEvent, MyAssembly" or "MyNamespace.TakeoffEvent, MyAssembly". Therefore I will probably need to instantiate the returned object myself based on this value. I am not sure how, though.
You can use a custom JsonConverter to do double duty in handling both the polymorphic event types and the varying JSON formats. Below is an example. It works by loading the data into a JObject, where it can read the $type property and instantiate the correct event type. From there, it will try to populate the event object from the JSON. If the Airport fails to deserialize, it will then attempt to read the legacy airport proprties and populate a new Airport instance from that.
class EventConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseEvent).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
string type = (string)obj["$type"];
BaseEvent baseEvent;
if (type.Contains(nameof(TakeoffEvent)))
{
baseEvent = new TakeoffEvent();
}
else
{
baseEvent = new LandingEvent();
}
serializer.Populate(obj.CreateReader(), baseEvent);
if (baseEvent.Airport == null)
{
baseEvent.Airport = new Airport
{
Name = (string)obj["AirportName"],
Runway = (string)obj["AirportRunway"]
};
}
return baseEvent;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Note: this assumes your class structure actually looks like this:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class BaseEvent
{
public Airport Airport { get; set; }
}
class TakeoffEvent : BaseEvent
{
public DateTime TimeOfTakeoff { get; set; }
}
class LandingEvent : BaseEvent
{
public DateTime TimeOfLanding { get; set; }
}
To use the converter, add it to the Converters collection in the JsonSerializerSettings, and pass the settings to DeserializeObject():
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = new List<JsonConverter> { new EventConverter() }
};
var baseEvent = JsonConvert.DeserializeObject<BaseEvent>(json, settings);
Here is a working demo: https://dotnetfiddle.net/jSaq4T
See also: Adding backward compatibility support for an older JSON structure
Classes change, this kind of Json strings change and will get extra features in future versions. You'll keep adjusting your declarations. With Newtonsoft, you can add custom handlers for varying class inheritance and keep using deserialize, but you'll have to maintain that code.
For dynamic Json, I find it easier to use JObject, JArray and JToken instead, to freely parse a Json string. Especially if you're only interested in some of the fields.
I can only give you an example, I think this is (a little) related to your project, but not the same part (smiley)
I use below code to decode part of a glTF 3d-object file produced by Blender in MSFS-converted format. This Json-like format consists of sections. Each Json section looks something like this,
"asset" : {
"extensions" : {
"ASOBO_normal_map_convention" : {
"tangent_space_convention" : "DirectX"
}
},
"generator" : "Extended Khronos glTF Blender I/O v1.0.0",
"version" : "2.0"
},
.. but these sections and their fields are mostly optional and in some GLtf's they are not filled in. It is not "serializable or deserializable" to classes.
I declare some
public JObject AssetObject;
.. filling it in from Json string sJson as follows:
dynamic stuff = JObject.Parse(sJson);
var pp = stuff.Children();
Dictionary<string, bool> d = new Dictionary<string, bool>();
foreach (JProperty jo in pp) d[jo.Name] = true; // all sections
string cSection= "asset";
if (!d.ContainsKey(cSection)) { LogLine(98, "Warning: BPG Json has no " + cSection + " section."); return false; }
else
{
AssetObject = (JObject)stuff[cSection];
ParseGLBAsset();
}
Notice the use of a dynamic declaration at first, a section will land in JObject via cast. I store the various parts of the section into string properties. The parse itself takes place in ParseGLBAsset(), this function looks as follows:
public void ParseGLBAsset()
{
foreach (JProperty jbp in AssetObject.Children())
if (jbp.Name == "generator")
{ GLBGenerator = jbp.Value.ToString(); }
else
if (jbp.Name == "extensions")
{
GLBAssetExtensions = jbp.Value.ToString();
LogLine(0, "Asset extensions: " + GLBAssetExtensions);
}
else
if (jbp.Name == "version")
{ GLBVersion = jbp.Value.ToString(); }
LogLine(1, "Found asset.generator=" + GLBGenerator);
LogLine(1, "Found asset.version=" + GLBVersion);
}
This question already has answers here:
Json.net failing to load certain properties belonging to a class object?
(2 answers)
Closed 1 year ago.
I have the following custom JsonConverter:
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;
using System;
namespace CSTool.Json
{
public class TextSpanJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(TextSpan);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var str = reader.ReadAsString();
var delim = str.IndexOf('.');
var start = int.Parse(str.AsSpan(1, delim - 1));
var end = int.Parse(str.AsSpan(delim + 2, str.Length - delim - 3));
return TextSpan.FromBounds(start, end);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}
}
It is supposed to help (de)serialize the following class:
using CSTool.Json;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;
namespace CSTool.ObjectModel
{
public class RefLocCacheItem
{
public string FilePath { get; private set; }
[JsonConverter(typeof(TextSpanJsonConverter))]
public TextSpan TextSpan { get; private set; }
[JsonConverter(typeof(LinePositionSpanJsonConverter))]
public LinePositionSpan LinePositionSpan { get; private set; }
public RefLocCacheItem()
{
}
public RefLocCacheItem(Location o) : this(o.SourceTree.FilePath, o.SourceSpan, o.GetLineSpan().Span)
{
}
public RefLocCacheItem(string filePath, TextSpan textSpan, LinePositionSpan linePositionSpan)
{
FilePath = filePath;
TextSpan = textSpan;
LinePositionSpan = linePositionSpan;
}
}
}
The deserialization code is:
cached = JsonConvert.DeserializeObject<Dictionary<uint, List<RefLocCacheItem>>>(File.ReadAllText(cacheFile));
The respective serialization code is:
File.WriteAllText(cacheFile, JsonConvert.SerializeObject(cached, Newtonsoft.Json.Formatting.Indented));
And here is a sample json file:
{
"100666494": [],
"100666517": [],
"67111627": [
{
"FilePath": "c:\xyz\\tip\\MySourceFile.cs",
"TextSpan": "[105331..105379)",
"LinePositionSpan": "(2379,51)-(2379,99)"
}
],
"67111628": [
{
"FilePath": "c:\xyz\\tip\\MySourceFile.cs",
"TextSpan": "[136762..136795)",
"LinePositionSpan": "(2953,30)-(2953,63)"
}
],
"100666534": []
}
So, as you can see, serialization works fine. However, the deserialization code never invokes the converter's ReadJson function. In fact, it does not work at all! There is no failure, but the returned dictionary contains RefLocCacheItems with null file paths and empty text spans:
I used Json.Net many times in the past and I don't understand what am I doing wrong now.
I use the latest version - 13.0.1, but I checked a few older versions - same thing. So, it is my fault, but where?
Clarification edit
The FilePath property is not deserialized. And it has nothing to do with the converter. And the converter - the ReadJson method is not even called!
The problem is your properties have private setters. As such, Json.Net will treat them as read-only. You can either make the setter public, or, if you wish to keep the setters private, you can add a [JsonProperty] attribute to the property to tell Json.Net that it is OK to use the private setter:
[JsonProperty]
public string FilePath { get; private set; }
[JsonProperty, JsonConverter(typeof(TextSpanJsonConverter))]
public TextSpan TextSpan { get; private set; }
I'm getting a really strange situation where I'm trying to serialize an object returned by a third party API into JSON. I don't have any control over the third party API or the object it returns. The C# POCO I'm trying to serialize looks something like this:
public class JobSummary {
public Job Job { get; set; }
}
public class Job {
public Status Status { get; set; }
}
public class Status {
public object JobOutput { get; set; }
public int Progress { get; set; }
}
Based on what the third party library returns, I would expect it to serialize to the following. At runtime, I can tell that the type of JobOutput is a JObject that contains a single key (Count) and value (0).
{
job: {
status: {
jobOutput: {
Count: 0
},
progress: 100
}
}
}
In this, job and status are obviously objects. progress is an int and jobOutput is a JObject.
If I run any of the following variations:
JToken.FromObject(jobSummary)
JObject.FromObject(jobSummary)
JObject.Parse(jobSummary)
And ToString() or JsonConvert.SerializeObject() the result, I get the following output:
{
job: {
status: {
jobOutput: {
Count: []
},
progress: 100
}
}
}
Notice that Count has become an [].
But if I do jobSummary.Status.JobOutput.ToString(), I correctly get back 0, so I know that the POCO returned by the third party library isn't malformed and has the info I need.
Does anybody know what could be going on? Or how I can correctly serialize the nested JObject?
Edit: I should clarify that I'm on v6.0.8 of Newtonsoft for reasons outside my control, and that the thirdparty assembly that contains the POCO has an unknown version of Newtonsoft ILMerged in it. I don't know if that is relevant.
You wrote that
I should clarify that I'm on v6.0.8 of Newtonsoft for reasons outside my control, and that the thirdparty assembly that contains the POCO has an unknown version of Newtonsoft ILMerged in it.
This explains your problem. The JobOutput contains an object with full name Newtonsoft.Json.Linq.JObject from a completely different Json.NET DLL than the one you are using. When your version of Json.NET tests to see whether the object being serialized is a JToken, it checks objectType.IsSubclassOf(typeof(JToken)) -- which will fail since the ILMerged type is not, in fact, a subclass of your version's type, despite having the same name.
As a workaround, you will need to create custom JsonConverter logic that uses the ToString() methods of the foreign JToken objects to generate output JSON, then writes that JSON to the JSON stream you are generating. The following should do the job:
public class ForeignJsonNetContainerConverter : ForeignJsonNetBaseConverter
{
static readonly string [] Names = new []
{
"Newtonsoft.Json.Linq.JObject",
"Newtonsoft.Json.Linq.JArray",
"Newtonsoft.Json.Linq.JConstructor",
"Newtonsoft.Json.Linq.JRaw",
};
protected override IReadOnlyCollection<string> TypeNames { get { return Names; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var json = value.ToString();
// Fix indentation
using (var stringReader = new StringReader(json))
using (var jsonReader = new JsonTextReader(stringReader))
{
writer.WriteToken(jsonReader);
}
}
}
public class ForeignJsonNetValueConverter : ForeignJsonNetBaseConverter
{
static readonly string [] Names = new []
{
"Newtonsoft.Json.Linq.JValue",
};
protected override IReadOnlyCollection<string> TypeNames { get { return Names; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var underlyingValue = ((dynamic)value).Value;
if (underlyingValue == null)
{
writer.WriteNull();
}
else
{
// JValue.ToString() will be wrong for DateTime objects, we need to serialize them instead.
serializer.Serialize(writer, underlyingValue);
}
}
}
public abstract class ForeignJsonNetBaseConverter : JsonConverter
{
protected abstract IReadOnlyCollection<string> TypeNames { get; }
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive)
return false;
// Do not use the converter for Native JToken types, only non-native types with the same name(s).
if (objectType == typeof(JToken) || objectType.IsSubclassOf(typeof(JToken)))
return false;
var fullname = objectType.FullName;
if (TypeNames.Contains(fullname))
return true;
return false;
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then use them in settings as follows:
var settings = new JsonSerializerSettings
{
Converters =
{
new ForeignJsonNetContainerConverter(), new ForeignJsonNetValueConverter()
},
};
var json = JsonConvert.SerializeObject(summary, Formatting.Indented, settings);
Notes:
The converters work by assuming that types whose FullName matches a Json.NET type's name are, in fact, Json.NET types from a different version.
JValue.ToString() returns localized values for DateTime objects (see here for details), so I created a separate converter for JValue.
I also fixed the indentation to match.
Mockup fiddle here.
My JSON looks like
{
"d": {
"__type": "CubeJsonData",
"SessionID": null,
"Code": 0,
"Message": "",
"Rows": {},
"Columns": {
"Place 1": [
0,
1
],
"Place 2": [
0,
2,
4,
6
],
},
"Set": [
[
"Number 1"
],
[
"Number 2"
],
[
"Number 3"
]
]
}
}
I need to get the following values
List<string> Columns must contain: "Place 1", "Place 2"
List<string> Set must contain: "Number 1", "Number 2", "Number 3"
My caller is
var settings = new JsonSerializerSettings();
settings.Converters.Add(new AssosiativeArrayConverter());
var staffAlerts = JsonConvert.DeserializeObject<List<AlertDetails>>(jo.ToString(), settings);
My JsonConverter is
class AssosiativeArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(string)) || (objectType == typeof(List<string>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
var l = new List<string>();
reader.Read();
while (reader.TokenType != JsonToken.EndArray)
{
l.Add(reader.Value as string);
reader.Read();
}
return l;
}
else
{
return new List<string> { reader.Value as string };
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{//to do
}
}
My class is
class StaffDetail
{
public string __type { get; set; }
public string SessionID { get; set; }
public string Code { get; set; }
public string Message { get; set; }
[JsonConverter(typeof(AssosiativeArrayConverter))]
public List<string> Rows { get; set; }
[JsonConverter(typeof(AssosiativeArrayConverter))]
public List<string> Columns { get; set; }
[JsonConverter(typeof(AssosiativeArrayConverter))]
public List<string> Set { get; set; }
}
I am getting an error
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ReportingServicesAlerts.AlertDetails]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Can you help me figure out what I'm doing wrong?
You have a lot of problems going on here. Let me start with the reason you are getting this error: your JSON contains a single outer object, but you are trying to deserialize it into a list. This won't work. If there's just a single object in the JSON, you need to deserialize into a single object.
Second issue, the data for your StaffDetail class is not at the top level of your JSON; it is one level down-- in the value of the d property of the outer JSON object. To fix this, you need to introduce a wrapper class and deserialize into that. Then you can retrieve the StaffDetail from the wrapper.
Third issue, it looks like you are trying to flatten the Columns and Set data from the JSON into List<string> properties in your class. It looks like you have correctly realized that you need a converter to do this; however, your converter doesn't handle the JSON data correctly-- it is assuming it is going to get just a simple array of strings or a simple string. Neither Columns nor Set is structured in this way in the JSON, and furthermore, the data is structured differently for both of them. The former is an object containing properties whose values are arrays of integers. The latter is an array of arrays of strings. Since they are different structures, they need to be handled differently. I would suggest using two different converters in this case.
Fourthly, while you correctly decorate your StaffDetail class with [JsonConverter] attributes to indicate which properties should use your converter, you incorrectly also add the converter to the serializer settings. The problem with this is that your converter says in CanConvert that it can handle any string or any list of strings. If you apply the converter in the settings that means that Json.Net will try to use your converter for any property anywhere that is either a string or a list of strings. Clearly, you do not want this-- your converter is really intended just to handle one specific case.
Fifth, it looks like you have also decorated your Rows property with a [JsonConverter] attribute, but the JSON shows an empty object. Will this field have any data that you care about? If not, just declare it as object; if you do care, please show an example of what might be in there. Or if you know that it will be structured the same as either Columns or Set, then you can keep it as List<string> and reuse one of those converters.
There are also some other minor issues in your question such as your JSON being invalid due to an extra comma (already pointed out by #frno), and the fact that your call to JsonConvert.DeserializeObject() mentions a class called AlertDetails but the class you show is actually named StaffDetail. But we'll chalk those up to simple copy-paste mistakes.
Whew!
OK, so how do we fix all this?
Let start with your classes. I mentioned that you need a wrapper class since your data is actually one level down in the JSON; here's what that would look like:
class Wrapper
{
public StaffDetail d { get; set; }
}
For your StaffDetail class, I've changed the Columns and Set properties to use different converters, since the JSON is different for each. I'll define those converters next. I also changed the type of Rows to object and removed the [JsonConverter] attribute for now, since it's not clear from the question how that field should be handled. If the data will be structured like Columns or Set then you can change it back and use the appropriate converter, as I mentioned.
class StaffDetail
{
public string __type { get; set; }
public string SessionID { get; set; }
public string Code { get; set; }
public string Message { get; set; }
public object Rows { get; set; }
[JsonConverter(typeof(ColumnsConverter))]
public List<string> Columns { get; set; }
[JsonConverter(typeof(SetConverter))]
public List<string> Set { get; set; }
}
Here is the converter which will handle the Columns data. This converter will take a JSON object and extract the property names into a list of strings.
class ColumnsConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// this converter can handle converting some JSON to a List<string>
return objectType == typeof(List<string>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Convert an object to a flat list of strings containing
// just the property names from the object.
JObject obj = JObject.Load(reader);
return obj.Properties().Select(p => p.Name).ToList();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is the converter which will handle the Set data. This converter will take an array of arrays of strings and convert it into a flat list of strings.
class SetConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// this converter can handle converting some JSON to a List<string>
return objectType == typeof(List<string>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Convert an array of arrays of strings to a flat list of strings
JArray array = JArray.Load(reader);
return array.Children<JArray>()
.SelectMany(ja => ja.Children(), (ja, jt) => jt.Value<string>()).ToList();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To deserialize, you can call JsonConvert.DeserializeObject() like this. Notice how I deserialize into the Wrapper class, then retrieve the StaffDetail from it. Also notice that I don't need to (and shouldn't in this case) pass the converters to the deserializer. They will get picked up automatically and at the appropriate times by virtue of the [JsonConverter] attributes on the StaffDetail class properties.
StaffDetail detail = JsonConvert.DeserializeObject<Wrapper>(json).d;
Here is simple demo program to show how it all works:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""d"": {
""__type"": ""CubeJsonData"",
""SessionID"": null,
""Code"": 0,
""Message"": """",
""Rows"": {},
""Columns"": {
""Place 1"": [
0,
1
],
""Place 2"": [
0,
2,
4,
6
]
},
""Set"": [
[
""Number 1""
],
[
""Number 2""
],
[
""Number 3""
]
]
}
}";
StaffDetail detail = JsonConvert.DeserializeObject<Wrapper>(json).d;
Console.WriteLine("Columns: " + string.Join(", ", detail.Columns));
Console.WriteLine("Set: " + string.Join(", ", detail.Set));
}
}
Output:
Columns: Place 1, Place 2
Set: Number 1, Number 2, Number 3
your Json is a bit weird, if you can change it. Nevertheless, correct classes :
(+ DONT FORGET TO REMOVE A COMMA I WROTE ABOUT )
public class Columns
{
[JsonProperty(PropertyName="Place 1")]
public List<int> Place1;
[JsonProperty(PropertyName="Place 2")]
public List<int> Place2;
}
public class Rows { }
public class D
{
public string __type;
public object SessionID;
public int Code;
public string Message;
public Rows Rows;
public Columns Columns;
public List<List<string>> Set;
}
public class StaffDetail
{
public D d { get; set; }
}
and a single simple way to get it all
var result = JsonConvert.DeserializeObject<StaffDetail>(json);
then just get the properties all you want, like
result.d.Columns.Place1[0] // for example
I'm using JSON.NET to serialize some of my objects, and i'd like to know if there is a simple way to override the default json.net converter only for a specific object?
Currently I have the following class:
public class ChannelContext : IDataContext
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<INewsItem> Items { get; set; }
}
JSON.NET currently serializes the above like:
{
"Id": 2,
"Name": "name value",
"Items": [ item_data_here ]
}
Is it possible just for that specific class to format it this way instead:
"Id_2":
{
"Name": "name value",
"Items": [ item data here ]
}
I'm kinda new to JSON.NET.. I was wondering if the above has something to do with writing a custom converter. I wasn't able to find any concrete examples on how to write one, If anyone can point me out to a specific source, I'll really appreciate it.
I need to find a solution which makes that specific class always convert the same, because the above context is a part of an even bigger context which the JSON.NET default converter converts just fine.
Hope my question is clear enough...
UPDATE:
I've found how to create a new custom converter (by creating a new class which inherits from JsonConverter and override it's abstract methods), I overriden the WriteJson method as follows:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ChannelContext contextObj = value as ChannelContext;
writer.WriteStartObject();
writer.WritePropertyName("id_" + contextObj.Id);
writer.WriteStartObject();
writer.WritePropertyName("Name");
serializer.Serialize(writer, contextObj.Name);
writer.WritePropertyName("Items");
serializer.Serialize(writer, contextObj.Items);
writer.WriteEndObject();
writer.WriteEndObject();
}
This indeed does the job successfully, but...
I'm intrigued if there's a way to serialize the rest of the object properties by reusing the default JsonSerializer (or converter for that matter) instead of manually "Writing" the object using the jsonwriter methods.
UPDATE 2:
I'm trying to get a more generic solution and came up with the following:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
// Write associative array field name
writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(value));
// Remove this converter from serializer converters collection
serializer.Converters.Remove(this);
// Serialize the object data using the rest of the converters
serializer.Serialize(writer, value);
writer.WriteEndObject();
}
This works fine when adding the converter manually to the serializer, like this:
jsonSerializer.Converters.Add(new AssociativeArraysConverter<DefaultFieldNameResolver>());
jsonSerializer.Serialize(writer, channelContextObj);
But doesn't work when using the [JsonConverter()] attribute set to my custom coverter above the ChannelContext class because of a self reference loop that occurs when executing:
serializer.Serialize(writer, value)
This is obviously because my custom converter is now considered the default converter for the class once it is set with the JsonConverterAttribute, so I get an inifinite loop.
The only thing I can think of, in order to solve this problem is inheriting from a basic, jsonconverter class, and calling the base.serialize() method instead...
But is such a JsonConverter class even exists?
Thanks a lot!
Mikey
If anyone's interested in my solution:
When serializing certain collections, I wanted to create an associative json array instead of a standard json array, so my colleague client side developer can reach those fields efficiently, using their name (or key for that matter) instead of iterating through them.
consider the following:
public class ResponseContext
{
private List<ChannelContext> m_Channels;
public ResponseContext()
{
m_Channels = new List<ChannelContext>();
}
public HeaderContext Header { get; set; }
[JsonConverter(
typeof(AssociativeArraysConverter<ChannelContextFieldNameResolver>))]
public List<ChannelContext> Channels
{
get { return m_Channels; }
}
}
[JsonObject(MemberSerialization = MemberSerialization.OptOut)]
public class ChannelContext : IDataContext
{
[JsonIgnore]
public int Id { get; set; }
[JsonIgnore]
public string NominalId { get; set; }
public string Name { get; set; }
public IEnumerable<Item> Items { get; set; }
}
Response context contains the whole response which is written back to the client, like you can see, it includes a section called "channels", And instead of outputting the channelcontexts in a normal array, I'd like to be able to output in the following way:
"Channels"
{
"channelNominalId1":
{
"Name": "name value1"
"Items": [ item data here ]
},
"channelNominalId2":
{
"Name": "name value2"
"Items": [ item data here ]
}
}
Since I wanted to use the above for other contexts as well, and I might decide to use a different property as their "key", or might even choose to create my own unique name, which doesn't have to do with any property, I needed some sort of a generic solution, therefore I wrote a generic class called AssociativeArraysConverter, which inherits from JsonConverter in the following manner:
public class AssociativeArraysConverter<T> : JsonConverter
where T : IAssociateFieldNameResolver, new()
{
private T m_FieldNameResolver;
public AssociativeArraysConverter()
{
m_FieldNameResolver = new T();
}
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable).IsAssignableFrom(objectType) &&
!typeof(string).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEnumerable collectionObj = value as IEnumerable;
writer.WriteStartObject();
foreach (object currObj in collectionObj)
{
writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(currObj));
serializer.Serialize(writer, currObj);
}
writer.WriteEndObject();
}
}
And declared the following Interface:
public interface IAssociateFieldNameResolver
{
string ResolveFieldName(object i_Object);
}
Now all is left to do, is create a class which implements IAssociateFieldNameResolver's single function, which accepts each item in the collection, and returns a string based on that object, which will act as the item's associative object's key.
Example for such a class is:
public class ChannelContextFieldNameResolver : IAssociateFieldNameResolver
{
public string ResolveFieldName(object i_Object)
{
return (i_Object as ChannelContext).NominalId;
}
}