I have a number of properties that I'm trying to use Json.NET to serialize. Complex objects work fine as all I have to put above them are the [JsonProperty("name")] attributes.
Unfortunately, I have been struggling to get this to work with any enums. Ideally, I'd like to be able to define the property by just saying something like:
MyValue = ThisEnum.First;
and have the property within my class automatically serialize the enum to the appropriate changed value name (as this question asked: Control enum value format during (de)serialization via attributes. Unfortunately, I have written a number of custom converters and attempted to apply the suggestions of the people in one of my previous questions (Can't get enum to convert to json properly using Json.NET) and despite the respondents in that thread giving answers that look like they'd work, in my solution, they don't have any effect whatsoever.
For example, given this class of properties:
public class Animal
{
[JsonProperty("id")]
public string Id {get;set;}
[JsonProperty("types")]
[JsonConverter(typeof(StringEnumConverter))]
public AnimalType Types {get;set;}
[JsonProperty("created")]
[JsonConverter(typeof(MyDateTimeToSecondsSinceEpochConverter))]
public DateTime Created {get;set;}
}
Within the AnimalType enum:
public enum AnimalType
{
[EnumMember(Value="black_rhino")]
BlackRhino,
[EnumMember(Value="wild_pig")]
WildPig,
[EnumMember(Value="chicken")]
Chicken
}
Within the MyDateTimeToSecondsSinceEpochConverter class:
public class MyDateTimeToSecondsSinceEpochConverter : DateTimeConverterBase
{
public override WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(#"""\/Date(" + ConvertDateTimeToEpoch((DateTime)value).ToString() + #")\/""");
}
public override ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null) return null;
if (reader.TokenType == JsonToken.Integer)
return ConvertEpochToDateTime((long)reader.Value);
return DateTime.Parse(reader.Value.ToString());
}
public DateTime ConvertEpochToDateTime(long seconds)
{
return new DateTime(1970, 1, 1).AddSeconds(seconds);
}
public long ConvertDateTimeToEpoch(DateTime dateTime)
{
var epochStart = new DateTime(1970,1,1);
if (dateTime < epochStart) return 0;
return Convert.ToInt64(dateTime.Subtract(epochStart).TotalSeconds);
}
}
In my calling method, I can have something like:
var data = new Animal {
Id = "123abc",
Types = AnimalType.BlackRhino,
Created = new DateTime(2014,3,15)
}
I use a method through which I step through each of the properties of the object with the simple intention of listing each of them and the HTML encoded output of the value and putting them all in a string (that I'll later attach to a URL):
public static string GetString(Animal animal)
{
var result = "";
foreach( var property in animal.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
{
foreach(var attribute in property.GetCustomAttributes(false))
{
var name = (JsonPropertyAttribute)attribute;
var value = property.GetValue(animal, null);
if (value != null)
{
if (!string.Empty(result))
result += "&";
result += string.Format("{0}={1}", name.PropertyName, HttpUtility.UrlEncode(value.ToString()));
}
}
}
return result;
}
Unfortunately, when I run this, I see:
'id=abc123&type=BlackRhino&created=3%2f15%2f2014+12%3a00%3a00+AM'
Does anyone have any ideas as to how I can get the two JsonConverters to actually do some converting? The JsonProperty name is obviously changing, but 'BlackRhino' didn't change to 'black_rhino' nor did the DateTime convert to a long.
I'm ideally looking for something in attribute land or using these converters so that I don't have to write a get/set for every single enum I use in the project down the road, but can rather just apply the attribute and be done with it.
Thanks!
You can have Json.NET turn your object into a string and then into a dictionary, which is a collection of name/value pairs like a query string is.
public static string GetString(Animal animal)
{
var result = "";
var serialized = JsonConvert.SerializeObject(animal);
var dict = JsonConvert.DeserializeObject<IDictionary<string, string>>(serialized);
foreach (var pair in dict)
{
if (!string.IsNullOrEmpty(result))
result += "&";
result += string.Format("{0}={1}", pair.Key, HttpUtility.UrlEncode(pair.Value.ToString()));
}
return result;
}
With your example, this is:
id=123abc&types=black_rhino&created=%2fDate(1394841600)%2f
Related
I am currently working on a Custom JSON converter to be used in a WebAPI project. The requirement is - I have a DTO object having some properties. The APIs can be consumed by multiple clients. Depending upon a client few of my DTO Entities might have some additional data apart from the properties already present in the DTO Model. I need to create a custom JSON converter to Serialize and Deserialize this data.
//DTO
class AbcDTO
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public List<AdditionalProperty> AdditionalData { get; set; }
}
//AdditionalProperty class
class AdditionalProperty
{
public string Name { get; set; }
public object Value { get; set; }
}
//Request JSON Body
{
"Prop1": "Val1",
"Prop2": "Val2",
"AdditionalProp3": "Val3",
"AdditionalProp4": "Val4"
}
//After Deserialization the object should be as below
AbcDTO dto = {
Prop1 = "Val1",
Prop2 = "Val2",
AdditionalData = [
{ Name = "AdditionalProp3", Value = "Val3" },
{ Name = "AdditionalProp4", Value = "Val4" }]
}
//After Serialization of the above dto object the JSON should convert back to the Request JSON Body format
We don't want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we would need to keep the property as Dictionary<string, JToken> -- but we don't want to pass JToken to below layers.
Created a custom JSON converter -
class CustomJsonConverter : JsonConverter
{
bool _canWrite = true;
bool _canRead = true;
public override bool CanConvert(Type objectType)
{
return typeof(IEntity).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get
{
return _canWrite;
}
}
public override bool CanRead
{
get
{
return _canRead;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
PropertyInfo[] availablePropertyNames = objectType.GetProperties();
List<AdditionalProperties> additionalData = new List<AdditionalProperties>();
IEntity obj;
_canRead = false;
obj = (IEntity)jObject.ToObject(objectType);
_canRead = true;
IEnumerable<JProperty> properties = jObject.Properties();
foreach (JProperty prop in properties)
{
if (availablePropertyNames.Count(x => x.Name.Equals(prop.Name)) == 0)
{
AdditionalProperties addProp = new AdditionalProperties
{
Name = prop.Name,
Value = prop.Value.ToObject<object>(),
};
additionalData.Add(addProp);
}
}
obj.AdditionalData = additionalData;
return obj;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEntity obj = (IEntity)value;
List<AdditionalProperties> additionalData = obj.AdditionalData;
JObject jObj;
_canWrite = false;
jObj = (JObject)JToken.FromObject(obj);
_canWrite = true;
jObj.Remove("AdditionalData");
foreach (AdditionalProperties data in additionalData)
{
jObj.Add(data.Name, JToken.FromObject(data.Value));
}
jObj.WriteTo(writer);
}
}
WebAPI ContractResolver creates 1 JSON converter per Entity. Now the issue is _canRead and _canWrite are not thread-safe. Need to use them to use the base implementation provided by Newtonsoft. If we don't use them, the ToObject and FromObject method again calls the custom converter methods internally resulting in infinite recursion. Using them with logs, reduces performance. Is there any way we can create a custom converter using the base implementation of Newtonsoft.JSON serialization/deserialization without using canRead and canWrite flags?
I can also have reference type child properties - say Person contains Address. I want to capture additional data for both Parent and Child entities. The additional data will not contain data of reference type.
It's possible to disable the converter using a thread static variable or ThreadLocal<T> member, as shown in JSON.Net throws StackOverflowException when using JsonConvert or Generic method of modifying JSON before being returned to client. However, I'd like to suggest a simpler way of solving your problem.
You wrote, We dont want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we need to keep the property as Dictionary and we dont want to pass JToken to below layers. It is not necessary for the extension data dictionary to have values of type JToken. Values of type object are supported for extension data dictionaries, e.g.:
class AbcDTO
{
public AbcDTO() { this.AdditionalData = new Dictionary<string, object>(); }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
[JsonExtensionData]
public Dictionary<string, object> AdditionalData { get; private set; }
}
When the extension data dictionary is of type Dictionary<string, object>, Json.NET will deserialize JSON primitive values to their equivalent .Net primitives -- string, bool, long and so on -- rather than to JValue objects. Only when encountering an additional property whose value is a JSON object or array will a JToken be added to the dictionary, in which case you can use the answers from How do I use JSON.NET to deserialize into nested/recursive Dictionary and List? to convert the JToken to a conventional .Net type. (However, your question states that The additional data will not contain data of reference type, so this should not be necessary.)
Using [JsonExtensionData] in this manner completely avoids the need for a converter while also deserializing primitives as per your requirements, and thus seems much simpler than the original design shown in the question.
Sample .Net fiddle demonstrating that extension properties can be deserialized into AbcDTO and asserting that none of them are of type JToken.
I have a custom JSON converter, because 3rd party API accept specific structure only.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bodyRequest = (IRequest) value;
writer.WriteStartObject();
writer.WritePropertyName(bodyRequest.RequestName);
writer.WriteStartObject();
writer.WritePropertyName("-xmlns");
writer.WriteValue("http://example.com");
writer.WritePropertyName(bodyRequest.WrapperName);
writer.WriteStartObject();
var contractResolver = new CamelCasePropertyNamesContractResolver {
IgnoreSerializableAttribute = false
};
var properties = value.GetType().GetProperties();
foreach(var propertyInfo in properties) {
if (HasAttribute(propertyInfo, typeof(JsonIgnoreAttribute))) {
continue;
}
var propValue = propertyInfo.GetValue(value);
var propertyName = contractResolver.GetResolvedPropertyName(propertyInfo.Name);
writer.WritePropertyName(propertyName);
writer.WriteValue(propValue);
}
writer.WriteEndObject();
writer.WriteEndObject();
writer.WriteEndObject();
}
So, I create strcuture with help of WriteXXX methods, then I get all properties and serialize them. All works fine, but I need to handle Enums. For example, we have the following request model:
public class ExampleRequest : IRequest
{
public long Id{ get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public CarType CarType { get; set; }
public string RequestName => "Request";
public string WrapperName => "SendData";
}
public enum CarType
{
[EnumMember(Value = "NEW_CAR")
New,
[EnumMember(Value = "OLD_CAR")
Old
}
Currently, after serialization I see that CarType has numeric value 0 or 1,I understand that I use reflection and StringEnumConverter is ignored. How to properly serialize in this case?
Inside your loop, check whether the property has a JsonConverterAttribute applied. If so, get the type of converter, instantiate it and then call its WriteJson method instead of writing the value directly. Otherwise, just write the value as you are doing now.
In other words, replace this line:
writer.WriteValue(propValue);
with this:
var att = propertyInfo.GetCustomAttribute<JsonConverterAttribute>();
if (att != null)
{
var converter = (JsonConverter)Activator.CreateInstance(att.ConverterType);
converter.WriteJson(writer, propValue, serializer);
}
else
{
writer.WriteValue(propValue);
}
I would like to serialize all nulls from string and nullable types to empty strings. I looked into using a custom JsonConverter but properties with nulls are not passed into the converter. I looked into using a contract resolver and value provider but it won't let me set the value of an int? to an empty string.
What seems most promising is using a custom JsonWriter that overrides the WriteNull method, but when I instantiate that in the converter's WriteJson method, it doesn't write anything out.
public class NullJsonWriter : JsonTextWriter
{
public NullJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WriteNull()
{
base.WriteValue(string.Empty);
}
}
public class GamesJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<IGame>).IsAssignableFrom(objectType);
}
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)
{
writer = new NullJsonWriter(new StringWriter(new StringBuilder()));
writer.WriteStartObject();
if (typeof (IEnumerable).IsAssignableFrom(value.GetType()))
{
var list = (IEnumerable<IGame>)value;
if (!list.Any())
{
writer.WriteEndObject();
return;
}
var properties = list.First().GetType().GetProperties();
PropertyInfo gameId = null;
foreach (var prop in properties)
{
if (prop.Name == "GameId")
{
gameId = prop;
break;
}
}
for (int i = 0; i < list.Count(); i++)
{
writer.WritePropertyName(string.Format("{0}", gameId.GetValue(list.ElementAt(i))));
serializer.Serialize(writer, list.ElementAt(i));
}
}
writer.WriteEndObject();
}
I can see JSON text in the StringBuilder of the writer but it doesn't actually get written out to my webpage.
I'm not sure I understand the reasoning of why you want to write out an empty string in place of all nulls, even those that might represent a Nullable<int>, or other object. The problem with that strategy is it makes the JSON more difficult to deal with during deserialization: if the consumer is expecting a Nullable<int> and they get a string instead, it leads to errors, confusion, and essentially forces the consuming code to use a special converter or weakly typed objects (e.g. JObject, dynamic) to get around the mismatch. But let's put that aside for now and say that you have your reasons.
You can definitely do what you want by subclassing the JsonTextWriter class and overriding the WriteNull method to write an empty string instead. The trick is that you must instantiate your own JsonSerializer instead of using JsonConvert.SerializeObject, since the latter does not have any overloads that accept a JsonWriter. Here is a short proof-of-concept that shows that this idea will work:
class Program
{
static void Main(string[] args)
{
// Set up a dummy object with some data. The object also has a bunch
// of properties that are never set, so they have null values by default.
Foo foo = new Foo
{
String = "test",
Int = 123,
Decimal = 3.14M,
Object = new Bar { Name = "obj1" },
Array = new Bar[]
{
new Bar { Name = "obj2" }
}
};
// The serializer writes to the custom NullJsonWriter, which
// writes to the StringWriter, which writes to the StringBuilder.
// We could also use a StreamWriter in place of the StringWriter
// if we wanted to write to a file or web response or some other stream.
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
using (NullJsonWriter njw = new NullJsonWriter(sw))
{
JsonSerializer ser = new JsonSerializer();
ser.Formatting = Formatting.Indented;
ser.Serialize(njw, foo);
}
// Get the JSON result from the StringBuilder and write it to the console.
Console.WriteLine(sb.ToString());
}
}
class Foo
{
public string String { get; set; }
public string NullString { get; set; }
public int? Int { get; set; }
public int? NullInt { get; set; }
public decimal? Decimal { get; set; }
public decimal? NullDecimal { get; set; }
public Bar Object { get; set; }
public Bar NullObject { get; set; }
public Bar[] Array { get; set; }
public Bar[] NullArray { get; set; }
}
class Bar
{
public string Name { get; set; }
}
public class NullJsonWriter : JsonTextWriter
{
public NullJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WriteNull()
{
base.WriteValue(string.Empty);
}
}
Here is the output:
{
"String": "test",
"NullString": "",
"Int": 123,
"NullInt": "",
"Decimal": 3.14,
"NullDecimal": "",
"Object": {
"Name": "obj1"
},
"NullObject": "",
"Array": [
{
"Name": "obj2"
}
],
"NullArray": ""
}
Fiddle: https://dotnetfiddle.net/QpfG8D
Now let's talk about why this approach did not work as expected inside your converter. When JSON.Net calls into your converter, it already has a JsonWriter instantiated, which gets passed into the writer parameter of the WriteJson method. Instead of writing to that instance, what you have done is overwritten this variable with a new NullJsonWriter instance. But remember, replacing the contents of a reference variable does not replace the original instance that variable refers to. You are successfully writing to your new writer instance in the converter, but all the output that you are writing is going into the StringBuilder which you instantiated at the beginning of the WriteJson method. Since you never take the JSON result from the StringBuilder and do something with it, it is simply thrown away at the end of the method. Meanwhile, Json.Net continues to use its original JsonWriter instance, to which you have not written anything inside your converter. So, that is why you are not seeing your customized JSON in the final output.
There are a couple of ways to fix your code. One approach is just to use a new variable when instantiating the NullJsonWriter in your converter instead of hijacking the writer variable. Then, at the end of the WriteJson method, get the JSON from the StringBuilder and write it to the original writer using WriteRawValue method on the original writer.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
StringBuilder sb = new StringBuilder();
using (var innerWriter = new NullJsonWriter(new StringWriter(sb))
{
innerWriter.WriteStartObject();
// ... (snip) ...
innerWriter.WriteEndObject();
}
writer.WriteRawValue(sb.ToString());
}
Another approach, depending on your intended result, is not to use a separate writer inside your converter at all, but instead to create the NullJsonWriter instance at the very beginning of serialization and pass it to the outer serializer (as I did in the proof-of-concept earlier in the post). When Json.Net calls into your converter, the writer that is passed to WriteJson will be your custom NullJsonWriter, so at that point you can write to it naturally without having to jump through hoops. Keep in mind, however, that with this approach your entire JSON will have the nulls replaced with empty strings, not just the IEnumerable<IGame> that your converter handles. If you are trying to confine the special behavior to just your converter, then this approach will not work for you. It really depends on what you are trying to accomplish-- do you want the nulls replaced everywhere or just in part of the JSON? I did not get a good sense of this from your question.
Anyway, hope this helps.
JsonSerializer _jsonWriter = new JsonSerializer
{
NullValueHandling = NullValueHandling.Include
};
Use this to make the serializer not ignore null values, then you should be able to override the serialization with the code you already have :)
I am working on a C# application which has a browser integrated with it.
The browser will send some data to C# in json format.
Some of the fields from json can be dserialized using javascript deserializer, but I have some data for which a custom deserializer is required, I need to register a deserializer for that but the thing is the custom deserializer must be called only for those special data and the default javascript deserializer must be called for other data, the special data can be identified from there target field's data type / name in C#. How can I achieve this.
something like this.
public class example
{
public string abc;
public someOtherDataType xyz;
public void example()
{
serializer = new JavaScriptSerializer();
// receive json string
serializer.RegisterConverters(new JavaScriptConverter[]
{
new System.Web.Script.Serialization.CS.CustomConverter()
});
//call deserializer
}
}
The json string will be something like
{
"abc" : "valueabc"
"xyz" : "valueXYZ"
}
Now the custom deserializer must be called only during deserializing xyz and default must be called for abc.
Thank you.
The difficulty here is that a JavaScriptConverter allows you to map a JSON object from and to a c# class -- but in your JSON, "xyz" is just a string, not an object. Thus you can't specify a converter for someOtherDataType and instead must specify converters for every class that contains an instance of someOtherDataType.
(Note that the custom converter functionality in Json.NET does not have this restriction. If you were willing to switch to that library you could write a JsonConverter converting all uses of someOtherDataType from and to a JSON string.)
To write such a JavaScriptConverter:
Override JavaScriptConverter.Deserialize
Create a second Dictionary<string, Object> filtering out the fields requiring custom conversion.
Call new JavaScriptSerializer.ConvertToType<T> to deserialize the standard fields from the filtered dictionary.
Manually convert the remaining fields.
Override SupportedTypes to return the container type.
Thus, in your example, you could do:
public class example
{
public string abc;
public someOtherDataType xyz;
}
// Example implementation only.
public class someOtherDataType
{
public string SomeProperty { get; set; }
public static someOtherDataType CreateFromJsonObject(object xyzValue)
{
if (xyzValue is string)
{
return new someOtherDataType { SomeProperty = (string)xyzValue };
}
return null;
}
}
class exampleConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new[] { typeof(example) }; }
}
// Custom conversion code below
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var defaultDict = dictionary.Where(pair => pair.Key != "xyz").ToDictionary(pair => pair.Key, pair => pair.Value);
var overrideDict = dictionary.Where(pair => !(pair.Key != "xyz")).ToDictionary(pair => pair.Key, pair => pair.Value);
// Use a "fresh" JavaScriptSerializer here to avoid infinite recursion.
var value = (example)new JavaScriptSerializer().ConvertToType<example>(defaultDict);
object xyzValue;
if (overrideDict.TryGetValue("xyz", out xyzValue))
{
value.xyz = someOtherDataType.CreateFromJsonObject(xyzValue);
}
return value;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
}
And then, to test:
public class TestClass
{
public static void Test()
{
// receive json string
string json = #"{
""abc"" : ""valueabc"",
""xyz"" : ""valueXYZ""
}";
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[]
{
new exampleConverter()
});
var example = serializer.Deserialize<example>(json);
Debug.Assert(example.abc == "valueabc" && example.xyz.SomeProperty == "valueXYZ"); // No assert
}
}
I have this class structure:
public class GraphDataItem<TCategory, TValue>
{
public TCategory Category { get; set; }
public TValue Value { get; set; }
public GraphDataItem(TCategory category, TValue value)
{
Category = category;
Value = value;
}
}
I have a collection of this items, that I serialize using something like this:
List<GraphDataItem<DateTime, int>> items = GetItems();
var json = JsonConvert.SerializeObject(items);
The json output that I get looks like this:
[{"Category":"2014-04-30T00:00:00","Value":1},
{"Category":"2014-05-01T00:00:00","Value":38},
{"Category":"2014-05-02T00:00:00","Value":18}]
I want the default DateTime serializing behaviour, that will produce something like:
[{"Category":/Date(1245398693390)/,"Value":1},
{"Category":/Date(1245398693390)/,"Value":38},
{"Category":/Date(1245398693390)/,"Value":18}]
I suspect that the library is calling Category's toString() instead of the standard method. What can I do?
Try using the DateFormatHandling and DateTimeZoneHandling settings when you serialize, e.g.:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
List<GraphDataItem<DateTime, int>> items = GetItems();
var json = JsonConvert.SerializeObject(items, settings);
The default date serialization format used by JSON.Net is Microsoft date format. So you should be getting dates like you want. Ensure that while serializing, you're not using IsoDateTimeConverter.
This post about different date serialization options might be helpful.
Also, if you're using latest JSON.Net, you can use serialization tracing to see what's going on inside. That may turn up helpful clues as to why this is happening or how you can force it to produce correct output.
If everything else fails, you may have to resort to your a custom JsonConverter:
public class CustomConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
...
if(value is DateTime) {
var d = value as DateTime;
serializer.Serialize(writer, d);
}
else {
serializer.Serialize(writer, value);
}
...
}
// other overrides
}