I'm using .NET/MyCouch to read/write plain POCO objects to a NoSQL store (couchdb). This works fine, and also works fine for objects with simple relations.
I would like to express relations as "ids" between types in NoSQL/MyCouch for a project I'm working on, and I'm wanting to learn more about how I would make the most use of JSon.NET for this (MyCouch uses Json.net).
e.g: Login has many Users
class Common {
public string _id;
public string _rev;
... more here, including ID generation ...
}
class Login : Common {
public string Name {get;set;}
public List<User> users {get;set;}
}
class User : Common {
public string email {get;set};
}
When saving a User yields a nested JSON doc:
{
"name": "Neil"
...
"users": [
{
"_id": "user:foo:something",
"_rev": "32-92cca77ee07b8149b8d3dd76f8c8b08d"
"email": "foo#there.com"
...
},
...
]
}
In the normal case, MyCouch (via Json.NET serialization) traverses the relationship and generates nested JSON. Yay.
'Common' is a base class containing the ID, and also stores the ID as a mix of object type:code:UUID (hence the 'user:foo:something' example above).
What I would rather do is generate object IDs to the users, but only for these kinds of relationships. Not for every object that inherits from Common (all of them do). I'm pretty sure I could add a converter for all Common objects, but figure that'd break the basic serialization of all objects. I only want to create "lists of IDs" for actual List relationships.
I imagine JSON doc output like this:
{
name: "Neil"
...
users: ["user:foo:something", ... ]
}
I can do this by customizing the Json.NET serializer:
[JsonConverterAttribute(typeof(ReferenceListConverter), typeof(User))]
public List<User> Users { get; set; }
This works great for output. I get exactly what I want.
Here's ReferenceListConverter:
public class ReferenceListConverter : JsonConverter
{
private Type expectedType;
public override bool CanRead => true;
public override bool CanWrite => true;
public ReferenceListConverter(Type expectedType)
{
this.expectedType = expectedType;
}
public override bool CanConvert(Type objectType)
{
return typeof(Common).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Get the _id, and _rev, and construct a real object from those
// In this case, it means making another request to the server.
var listType = typeof(List<>);
var list = listType.MakeGenericType(this.expectedType);
if (reader.TokenType != JsonToken.Null)
{
if (reader.TokenType == JsonToken.StartArray)
{
JToken token = JToken.Load(reader);
List<string> items = token.ToObject<List<string>>();
// Construct some items
// ID's are of form <typename>:<id>
MethodInfo program = typeof(Program).GetMethod("FetchObjectsFromStore", System.Reflection.BindingFlags.Static | BindingFlags.Public);
MethodInfo generic = program.MakeGenericMethod(this.expectedType);
string[] parameters = items.ToArray();
var result = generic.Invoke(null, new object[] { parameters });
return result;
}
}
return list;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Write out only the ID of the object
JArray array = new JArray();
if(value is IEnumerable<Common>)
{
foreach (Common item in (IEnumerable<Common>)value)
{
array.Add(JToken.FromObject(item._id));
}
}
array.WriteTo(writer);
}
}
1) Reading... is problematic. Basic. But it works for a simple case.
I am unable to inject my "db" class via IoC (I'm using SimpleInjector and havn't figured out how I can inject a specific JsonConverter, for a single field, using IoC). So ReferenceListConverter uses a static method to do it's subsequent lookups, which is "meh", but OK while I learn.
2) This doesn't take into account cycles. As soon as User <-> Logins, I have an infinite loop.
My question is to knowledgeable Json.NET people: Is there just "a better way" to do this? Customization of the Json.NET reference mechanisms? Other?
Related
So I am making an api call and I need to use JsonConvert.DeserializeObject to convert it to a class. The Json structure comes back as the following
{
"fcResponse": {
"responseData": {
"fcRequest": {
"mail": "Emails",
"outlookMail": "Outlook Emails",
(etc.)
}
}
}
}
The problem is that the values that come back inside "fcRequest" varies based on the parameters I am sending.
The class structure is as follows so far
public class GetSubModulesResponse : BaseResponse
{
[JsonProperty("fcResponse")]
public SubModuleResponse Response { get; set; }
}
public class SubModuleResponse
{
[JsonProperty("responseData")]
public SubModuleData Data { get; set; }
}
public class SubModuleData
{
[JsonProperty("fcRequest")]
public SubModuleFIMRequest RequestFIM { get; set; }
[JsonProperty("fcRequest")]
public SubModuleFSRequest RequestFS { get; set; }
}
And this is the basic call structure
GetSubModulesResponse subModuleResponse = new GetSubModulesResponse();
var response = SubmitAPICall();
subModuleResponse = JsonConvert.DeserializeObject<GetSubModulesResponse>(response);
Now I know I obviously can't have the same JsonProperty on both RequestFIM and RequestFS, but what I'm trying to do is somehow find a way to switch which one of those two properties I should use based on a variable.
One option is to go with a custom (de-)serializer for the element. This way, you can still least benefit from automatic deserialization in most spots and get the flexibility where you need it. I'm assuming you're using Newtonsoft JSON / JSON.NET.
Let's introduce a base class for the fcRequest elements first.
public enum ResponseType
{
FIM, FS
}
public abstract class ResponseBase
{
[JsonIgnore]
public abstract ResponseType ResponseType { get; }
}
By adding a ResponseType here you can simplify consuming code; if you can use type based pattern matching, you may not even need it.
I obviously have no idea what your domain entities are, but for the sake of the argument, the SubModuleFIMRequest is now going to contain the mail addresses. In addition, it also derives from said ResponseBase:
public class SubModuleFIMRequest : ResponseBase
{
public override ResponseType ResponseType => ResponseType.FIM;
[JsonProperty("mail")]
public string Mail { get; set; }
[JsonProperty("outlookMail")]
public string OutlookMail { get; set; }
}
Next, you'd implement a JsonConverter<ResponseBase>; to make life easy, it can deserialize the responseData content into a JObject first. In doing so, you'll be able to introspect the properties of the element, which in turn (hopefully) allows you to come up with a heuristic to determine the element's actual type.
Once you know the type, you convert the JObject to a concrete instance. Here's an example:
public class ResponseDataConverter : JsonConverter<ResponseBase>
{
/// <inheritdoc />
public override bool CanWrite => false;
/// <inheritdoc />
public override ResponseBase ReadJson(JsonReader reader, Type objectType, ResponseBase existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jObject = serializer.Deserialize<JObject>(reader);
// Now, decide tye type by matching patterns.
if (jObject.TryGetValue("mail", out var mailToken))
{
return jObject.ToObject<SubModuleFIMRequest>();
}
// TODO: Add more types as needed
// If nothing matches, you may choose to throw an exception,
// return a catchall type (e.g. wrapping the JObject), or just
// return a default value as a last resort.
throw new JsonSerializationException();
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, ResponseBase value, JsonSerializer serializer) => throw new NotImplementedException();
}
Note that the serializer doesn't need to write, so we're just throwing in WriteJson.
What's left is to annotate the SubModuleData's fcProperty property with a JsonConverter attribute pointing to the converter type:
public class SubModuleData
{
[JsonProperty("fcRequest")]
[JsonConverter(typeof(ResponseDataConverter))]
public ResponseBase FcRequest { get; set; }
}
I hope that gets you started. As was mentioned in other comments and answers: If you can influence the API returning the JSON in the first place, try changing that instead.
If you're in control of the Json being returned, I'd highly recommend returning each object under it's own property and set whatever's not needed to null.
If that's not an option, another thing that could work is deserializing "fcRequest" to dynamic and then try casting to the types it may be or casting based on another property name. That's not very clean.
Another interesting approach in a different Json lib (Jil) is Unions.
"Jil has limited support for "unions" (fields on JSON objects that may contain one of several types), provided that they can be distiguished by their first character."
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.
Appologies if its already been asked, I could not find anything helpful to my situation.
I need to deserialize a JSON in a property of my object instead of a whole object. The reason I am trying to do it, is that is simply generics.
I have the following situation
For instance I have
Class User
{
int UserId {get;set;}
string Name {get;set;
}
Class Wanted : CustomClass
{
User[] Users {get;set;}
public override void Map(){ }
public override void Scan(){ }
}
My Json is:
[
{
"userId": 1,
"name": "Josh"
},
{
"userId": 5,
"name" : "Martin"
}
]
Is it possible to deserialize(+ generics) my JSON directly into my Wanted class instead of serializing into A and then assign it into Wanted ?
The goal is after the serialization I will have object with type Wanted and an array with 2 users in it.
Since the JSON does not match the class you want to deserialize into, and you cannot change the JSON, you will need to use a custom JsonConverter to bridge the gap.
To make it work you'll need to introduce an interface IHasUsers which your Wanted class (or its base class) will need to implement:
interface IHasUsers
{
User[] Users { get; set; }
}
class Wanted : CustomClass, IHasUsers
{
public User[] Users { get; set; }
...
}
Then you can make a generic converter which will instantiate the Wanted class (or any other class which implements IHasUsers) and populate the Users property:
class UserListConverter<T> : JsonConverter where T: IHasUsers, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(IHasUsers).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
T obj = new T() { Users = array.ToObject<User[]>() };
return obj;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then you can deserialize your JSON like this:
Wanted wanted = JsonConvert.DeserializeObject<Wanted>(json, new UserListConverter<Wanted>());
Here is a demo: https://dotnetfiddle.net/KL6Ok6
Hope this is what you were looking for.
Since Wanted is "your desired class", there needs to be an instance of Wanted created somewhere. You might just as well create it yourself rather than having a derserializer do it for you. Once you have done this you can simply set the Users property to the deserialized data:
var wanted = new Wanted() { Users = JsonConvert.DeSerialize<User[]>(myString) };
You don't deserialize some data "into a property" without deserializing it to some object of some type first. Once you have done this you can then set the property to the object that contains the deserialized data.
There is nothing generic about Wanted here though and the deserializer cannot be supposed to figure out that it should create a Wanted or any other type unless you specify the type to derserialize the data to somewhere.
And there is no point of deserializing the data to a type defined at compile time if you don't know that the data matches this type. Then you might as well create an anonymous object or a dictionary of key/value pairs.
You can use Newtonsoft.json . Try below
var files = JArray.Parse(YourJSON);
var recList = files.SelectTokens("$").ToList();
foreach (JObject item in recList.Children())
{
foreach (JProperty prop in item.Children())
{
string key = prop.Name.ToString();
string value = prop.Value.ToString();
// and add these to an array
}
}
I am having trouble trying to determine how to make my Serialization Properly be able to access a single result, as well as an array.
When I make a REST call looking for something on a server, sometimes it will return an Array of models, but if the search results only have a single model, it will not be returned as an error. This is when I get an exception that I cannot deserialize because the Object Property is expecting an array, but is instead receiving a single object.
Is there a way to define my class so that it can handle a single object of type ns1.models when that is returned instead of an array of objects?
[JsonObject]
public class Responses
{
[JsonProperty(PropertyName = "ns1.model")]
public List<Model> Model { get; set; }
}
Response that can be deserialized:
{"ns1.model":[
{"#mh":"0x20e800","ns1.attribute":{"#id":"0x1006e","$":"servername"}},
{"#mh":"0x21a400","ns1.attribute":{"#id":"0x1006e","$":"servername2"}}
]}
Response that cannot be serialized (because JSON includes only a singe "ns1.model"):
{"ns1.model":
{"#mh":"0x20e800","ns1.attribute":{"#id":"0x1006e","$":"servername"}}
}
Exception:
Newtonsoft.Json.JsonSerializationException was unhandled HResult=-2146233088 Message=Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ConsoleApplication1.Model]' 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<T>) 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. Path '['ns1.model-response-list'].['ns1.model-responses'].['ns1.model'].#mh', line 1, position 130
To handle this you have to use a custom JsonConverter. But you probably already had that in mind.
You are just looking for a converter that you can use immediately. And this offers more than just a solution for the situation described.
I give an example with the question asked.
How to use my converter:
Place a JsonConverter Attribute above the property. JsonConverter(typeof(SafeCollectionConverter))
public class Response
{
[JsonProperty("ns1.model")]
[JsonConverter(typeof(SafeCollectionConverter))]
public List<Model> Model { get; set; }
}
public class Model
{
[JsonProperty("#mh")]
public string Mh { get; set; }
[JsonProperty("ns1.attribute")]
public ModelAttribute Attribute { get; set; }
}
public class ModelAttribute
{
[JsonProperty("#id")]
public string Id { get; set; }
[JsonProperty("$")]
public string Value { get; set; }
}
And this is my converter:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace stackoverflow.question18994685
{
public class SafeCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//This not works for Populate (on existingValue)
return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
And this converter uses the following class:
using System;
namespace Newtonsoft.Json.Linq
{
public static class SafeJsonConvertExtensions
{
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
{
return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
}
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
{
var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);
if (jToken is JArray jArray)
{
if (!expectArray)
{
//to object via singel
if (jArray.Count == 0)
return JValue.CreateNull().ToObject(objectType, jsonSerializer);
if (jArray.Count == 1)
return jArray.First.ToObject(objectType, jsonSerializer);
}
}
else if (expectArray)
{
//to object via JArray
return new JArray(jToken).ToObject(objectType, jsonSerializer);
}
return jToken.ToObject(objectType, jsonSerializer);
}
public static T ToObjectCollectionSafe<T>(this JToken jToken)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T));
}
public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
}
}
}
What does it do exactly?
If you place the converter attribute the converter will be used for this property. You can use it on a normal object if you expect a json array with 1 or no result. Or you use it on an IEnumerable where you expect a json object or json array. (Know that an array -object[]- is an IEnumerable)
A disadvantage is that this converter can only be placed above a property because he thinks he can convert everything. And be warned. A string is also an IEnumerable.
And it offers more than an answer to the question:
If you search for something by id you know that you will get an array back with one or no result.
The ToObjectCollectionSafe<TResult>() method can handle that for you.
This is usable for Single Result vs Array using JSON.net
and handle both a single item and an array for the same property
and can convert an array to a single object.
I made this for REST requests on a server with a filter that returned one result in an array but wanted to get the result back as a single object in my code. And also for a OData result response with expanded result with one item in an array.
Have fun with it.
I think your question has been answered already. Please have a look at this thread:
How to handle both a single item and an array for the same property using JSON.net .
Basically the way to do it is to define a custom JsonConvertor for your property.
There is not an elegant solution to your problem in the current version of JSON.NET. You will have to write custom parsing code to handle that.
As #boyomarinov said you can develop a custom converter, but since your JSON is pretty simple you can just parse your JSON into an object and then handle the two cases like this:
var obj = JObject.Parse(json);
var responses = new Responses { Model = new List<Model>() };
foreach (var child in obj.Values())
{
if (child is JArray)
{
responses.Model = child.ToObject<List<Model>>();
break;
}
else
responses.Model.Add(child.ToObject<Model>());
}
Use JRaw type proxy property ModelRaw:
public class Responses
{
[JsonIgnore]
public List<Model> Model { get; set; }
[JsonProperty(PropertyName = "ns1.model")]
public JRaw ModelRaw
{
get { return new JRaw(JsonConvert.SerializeObject(Model)); }
set
{
var raw = value.ToString(Formatting.None);
Model = raw.StartsWith("[")
? JsonConvert.DeserializeObject<List<Model>>(raw)
: new List<Model> { JsonConvert.DeserializeObject<Model>(raw) };
}
}
}
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;
}
}