I’m using Json.net to deserialize json data received by Web API call. Some fields often have html-encoded characters like " or & How can I have this characters automatically decoded during deserialization?
I came to 2 possible solutions:
Calling System.Web.HttpUtility.HtmlDecode() in property setter like:
public string Title
{
set
{
title = System.Web.HttpUtility.HtmlDecode(value);
}
}
Writing custom JsonConverter that calls System.Web.HttpUtility.HtmlDecode() in ReadJson() method:
public class HtmlEncodingConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(String);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return System.Web.HttpUtility.HtmlDecode((string)reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(System.Web.HttpUtility.HtmlEncode((string)value));
}
}
But is there any built-in solution that allows to perform html-decoding during json deserialization without additional code?
System.Net.WebUtility.HtmlDecode()
or
HttpUtility.HtmlDecode()
is the way to go, nothing built in regarding the JsonSerializer.
Related
I am writing a Cmdlet and need to pass object structures into an API client that may contain PSObjects. Currently, these serialise as a JSON string containing CLIXML. Instead, I need it to be treated like an object (including the NoteProperties in PSObject.Properties as properties, and recursively serialising their values).
I tried writing my own JsonConverter but for some reason it only gets called for the top level object, not for nested PSObjects:
public class PSObjectJsonConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
if (value is PSObject) {
JObject obj = new JObject();
foreach (var prop in ((PSObject)value).Properties) {
obj.Add(new JProperty(prop.Name, value));
}
obj.WriteTo(writer);
} else {
JToken token = JToken.FromObject(value);
token.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
throw new NotImplementedException();
}
public override bool CanRead {
get { return false; }
}
public override bool CanConvert(Type objectType) {
return true;
}
}
Additionally, I am using serializing to camel case using CamelCasePropertyNamesContractResolver. Is there a way to make the converter respect that?
The following converter should correctly serialize recursively nested objects of type PSObject:
public class PSObjectJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(PSObject).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var psObj = (PSObject)value;
writer.WriteStartObject();
foreach (var prop in psObj.Properties)
{
//Probably we shouldn't try to serialize a property that can't be read.
//https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.pspropertyinfo.isgettable?view=powershellsdk-1.1.0#System_Management_Automation_PSPropertyInfo_IsGettable
if (!prop.IsGettable)
continue;
writer.WritePropertyName(prop.Name);
serializer.Serialize(writer, prop.Value);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead { get { return false; } }
}
Notes:
In WriteJson you serialize the incoming object value as the value of each property. Surely you meant prop.Value.
By only returning true from CanConvert() when the incoming object type is of type PSObject, you avoid the need to implement default serialization for non-PSObject types in WriteJson().
When you call JToken.FromObject(value) you are not using the incoming JsonSerializer serializer. Thus, any JsonSerializerSettings (including converters) will be lost. In theory you could use JToken.FromObject(Object, JsonSerializer) instead, which would preserve settings, but if you did, you would encounter the bug described in JSON.Net throws StackOverflowException when using [JsonConvert()]. Luckily, since we now return false from CanConvert when default serialization is required, this is no longer necessary.
There is no need to construct an intermediate JObject. You can write directly to the JsonWriter, which will be somewhat more performant.
Update: Additionally, I am using serializing to camel case using CamelCasePropertyNamesContractResolver. Is there a way to make the converter respect that?
Once you introduce a custom JsonConverter for your type, you need to do everything manually, including remapping of property names. Here's a version of WriteJson() that handles this by using DefaultContractResolver.NamingStrategy:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var psObj = (PSObject)value;
writer.WriteStartObject();
var resolver = serializer.ContractResolver as DefaultContractResolver;
var strategy = (resolver == null ? null : resolver.NamingStrategy) ?? new DefaultNamingStrategy();
foreach (var prop in psObj.Properties)
{
//Probably we shouldn't try to serialize a property that can't be read.
//https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.pspropertyinfo.isgettable?view=powershellsdk-1.1.0#System_Management_Automation_PSPropertyInfo_IsGettable
if (!prop.IsGettable)
continue;
writer.WritePropertyName(strategy.GetPropertyName(prop.Name, false));
serializer.Serialize(writer, prop.Value);
}
writer.WriteEndObject();
}
Note that naming strategies were introduced in Json.NET 9.0.1 so if you are using an earlier version you will need to create your own camel case name mapper such as the one shown in this answer.
I'm using Json.NET to serialize validation data for data field. On the .NET side, validation data is a list of ValidationAttribute objects. However, I'd like to serialize them in a special form like this:
[
{ Type: 'Required', ErrorMessage: '{FieldName} is required' },
{ Type: 'RegularExpression', Pattern: '^\d+$', ErrorMessage: '...'
]
In an ideal solution I could simply intercept the object before serialization and, I could create a corresponding Dictionary<string, object> object to serialize instead of the original one.
Are there any solutions for this scenario?
You can implement your own JsonConverter class and convert your collection as you wish.
You just need to create you class and inherit it from JsonConverter
public class YourSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(YourClassName).IsAssignableFrom(objectType);
}
}
and then you need to decorate your class which will be serialized with the attribute (looks like it's not what you want)
[JsonConverter(typeof(YourSerializer))]
public class YourClassName
{
public string Name { get; set; }
public string Value { get; set; }
}
or, pass an instance of your serializer to Serialize methos:
string json = JsonConvert.SerializeObject(sourceObj, Formatting.Indented, new YourSerializer(typeof(yourClassName)));
Here is a few links:
http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/
Hope, it will help.
I'm using the official Mongo C# Driver, and RestSharp to call a Rest Api with Json.NET to perform my serialization/deserialization. Say I have a Person class as follows, which I'd like to POST & GET:
public class Person
{
[JsonProperty("_id"),JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id {get;set;}
public string Name {get;set;}
}
I create a new Person object:
var person = new Person{Id = ObjectId.GenerateId(),Name='Joe Bloggs'};
POST it, and on the server I see the following which is correct:
{ _id: 52498b56904ee108c99fbe88, name: 'Joe Bloggs'}
The problem, is when I perform a GET the ObjectId I get on the client is {0000000000000...}
i.e. not the {5249.....} I'd expect. The raw response is showing the correct value, but once I deserialize I loose it.
The ObjectIdConverter code is :
public class ObjectIdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var objectId = (ObjectId)existingValue; // at this point existingValue is {000...}
return objectId;
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof (ObjectId));
}
}
Any help would be appreciated.
You are implementing the ReadJson method of the converter incorrectly. The existingValue parameter does not give you the deserialized value read from the JSON, it gives you the existing value of the object that you will be replacing. In most cases this will be null or empty. What you need to do is use the reader to get the value from the JSON, convert it as needed, then return the converted value.
Assuming your ObjectId class has a constructor that accepts a hex string, here is how you would implement the ReadJson method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
return new ObjectId(token.ToObject<string>());
}
How can I customize the serialized output of ASP.NET Web API?
Let's say, I want all values in uppercase.
You have access to the JSON.NET serializer settings. With JSON.NET you can overide conversions using converters e.g. this datetime one.
You can also implement your own from inheritting from the abstract JsonConverter. See here for details.
For your example create the converter:
public class UpperCaseStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.Value.ToString();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var outputValue = value as string;
writer.WriteValue(outputValue == null ? null : outputValue.ToUpper());
}
}
And then to register this globally add this config:
Registration example from here
JsonMediaTypeFormatter jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
JsonSerializerSettings jSettings = new Newtonsoft.Json.JsonSerializerSettings();
jSettings.Converters.Add(new UpperCaseStringConverter());
jsonFormatter.SerializerSettings = jSettings;
To add to a single property on a model just add the annotation:
[JsonConverter(typeof(UpperCaseStringConverter))]
I'm using JSON.Net in the trial to serialize/deserialize a not-so-well-formed JSON.
Specifically I'm trying to serialize an attribute with a js variable as value, something like this:
{ ..., mapTypeId: google.maps.MapTypeId.ROADMAP, ... }
My c# classes are
public enum MapTypeId
{
HYBRID, ROADMAP, SATELLITE, TERRAIN
}
[JsonObject]
public class MapOptions
{
...
[JsonProperty]
[JsonConverter(typeof(MapTypeConverter))]
public MapTypeId mapTypeId { get; set; }
...
}
With a custom JsonConverter I've been able to control the serialization:
public class MapTypeConverter : JsonConverter
{
...
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue("google.maps.MapTypeId." + ((MapTypeId)value).ToString());
}
...
}
And the serialization works. But when I'm trying to deserialize the string with:
JsonConvert.DeserializeObject<MapOptions>(ser);
I get the error: Unexpected character encountered while parsing value: g. Path 'mapTypeId', line 1, position 169.
In my MapTypeConverter I've also tryed to override the ReadJson method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// no parse by now, just a try
return MapTypeId.TERRAIN;
}
without luck.
Suggestions?