Selectively escape HTML in strings during deserialization - c#

I'm looking to write a JsonConverter which escapes HTML in strings, unless the [AllowHtml] attribute has been applied;
private class ObjectWithStrings
{
// will be HTML-escaped
public string Name { get; set; }
// won't be escaped
[AllowHtml]
public string Unsafe { get; set; }
}
So I'm trying to write a JsonConverter with a custom ReadJson property;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var s = (string)reader.Value;
if (s == null)
{
return null;
}
// here I need to get a PropertyInfo so I can call GetCustomAttribute<AllowHtmlAttribute>();
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode(s, useNamedEntities: true);
return encoded;
}
The gap I've got is that I can't see if Json.Net will let me know the property I'm reading into. Consequently, I can't figure out how to get the property's custom attributes.
Is there a way to find out what property I'm serialising into, or a different pattern recommended for this kind of thing?
EDIT: I failed to write a clear question; I've attempted to write a JsonConverter which deserialises strings, -- see the implementation above of CanConvert(). I suspect that choice is the start of my problem; I may need to deserialise objects with string properties, and do a standard deserialize except when deserialising particular properties.

From within a custom JsonConverter, you can find the name of the JSON property being deserialized by picking it out of the Path property from the JsonReader.
string propertyName = reader.Path.Split('.').Last();
However, this will not solve your overall problem. Assuming the name of the JSON property matches your target class property, you'd still need a way to get the parent object type so you can get the custom attributes from it. Unfortunately, this information is not available to you inside a converter. A converter is intended to be responsible only for the object type it says it can convert (string in your case), and that object's child properties (none in this case, since string is a primitive). So, to make it work, the converter would need to be written to operate on the parent class, and would then need to handle all the string properties of that class. Since your goal seems to be to apply the HTML encoding behavior to all strings in all classes, then you would need a generic converter that handles all non-primitive types, which could get pretty messy, depending on the breadth of what you're trying to deserialize.
Fortunately, there is a better way. Instead of using a JsonConverter, you can use a custom IContractResolver in combination with a IValueProvider to solve this. A ContractResolver is much better suited to problems like this where you want to apply a certain behavior broadly.
Below is an example of the code you would need. The CustomResolver class extends the DefaultContractResolver provided by Json.Net. The CreateProperties() method inspects the JsonProperty objects created by the base resolver and attaches an instance of the inner HtmlEncodingValueProvider class to any string properties which do not have the [AllowHtml] attribute applied. Each value provider later handles the actual encoding of its target string property via the SetValue() method.
public class CustomResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
// Find all string properties that do not have an [AllowHtml] attribute applied
// and attach an HtmlEncodingValueProvider instance to them
foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
{
PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
if (pi != null && pi.GetCustomAttribute(typeof(AllowHtmlAttribute), true) == null)
{
prop.ValueProvider = new HtmlEncodingValueProvider(pi);
}
}
return props;
}
protected class HtmlEncodingValueProvider : IValueProvider
{
PropertyInfo targetProperty;
public HtmlEncodingValueProvider(PropertyInfo targetProperty)
{
this.targetProperty = targetProperty;
}
// SetValue gets called by Json.Net during deserialization.
// The value parameter has the original value read from the JSON;
// target is the object on which to set the value.
public void SetValue(object target, object value)
{
var encoded = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode((string)value, useNamedEntities: true);
targetProperty.SetValue(target, encoded);
}
// GetValue is called by Json.Net during serialization.
// The target parameter has the object from which to read the string;
// the return value is the string that gets written to the JSON
public object GetValue(object target)
{
// if you need special handling for serialization, add it here
return targetProperty.GetValue(target);
}
}
}
To use the resolver, create a new JsonSerializerSettings instance, then set its ContractResolver property to a new instance of the custom resolver and pass the settings to the JsonConvert.DeserializeObject() method.
Here is a short demo:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""Name"" : ""<b>Foo Bar</b>"",
""Description"" : ""<p>Bada Boom Bada Bing</p>"",
}";
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json, settings);
Console.WriteLine("Name: " + foo.Name);
Console.WriteLine("Desc: " + foo.Description);
}
}
class Foo
{
public string Name { get; set; }
[AllowHtml]
public string Description { get; set; }
}
class AllowHtmlAttribute : Attribute { }
Here is the output. Notice that the Name property gets HTML encoded while the Description property does not.
Name: <b>Foo Bar</b>
Desc: <p>Bada Boom Bada Bing</p>
Fiddle: https://dotnetfiddle.net/cAg4NC

Related

How to Override a Default JsonConverter (specified in an attribute)

I would like the following Author type to have a default JsonConverter, and be able to override it at runtime.
[JsonConverter(typeof(BaseJsonConverter))]
public class Author
{
// The ID of an author entity in the application.
public int ID { set; get; }
// The ID of an Author entity in its source.
public string SourceID { set; set; }
}
I used the following code to override the default converter (i.e., BaseJsonConverter).
public class AlternativeConverter : BaseJsonConverter
{ // the serializer implementation is removed for clarity. }
// Deserialize using AlternativeConverter:
var author = JsonConvert.DeserializeObject<Author>(jsonString, new AlternativeConverter());
Question
Using the above call, the AlternativeConverter is first constructed; however, then an instance of BaseJsonConverter is initialized and used for deserialization. So, the AlternativeConverter is never used.
Executable example: https://dotnetfiddle.net/l0bgYO
Use case
The application is to convert different JSON objects, obtained from different sources, to a common C# type. Commonly data comes from a source for that we define the default converter (i.e., BaseJsonConverter), and for data coming from other sources, we define different converters per each.
Background
I am aware of methods such as this one, and indeed I am using similar method partially. With ref to that article, I need to have different _propertyMappings depending on the source of input, because in my application attribute to property mapping is not one-to-one. For instance, I have the following JSON objects:
{
"id":123
}
// and
{
"id":"456"
}
where the first JSON object should be deserialized to:
author.ID = 123
author.SourceID = null
and the second JSON object should be deserialized as:
author.ID = 0
author.SourceID = "456"
You can use a custom ContractResolver to override a [JsonConverter] attribute programmatically. To solve your problem you could make a custom resolver like this:
public class CustomResolver : DefaultContractResolver
{
private Dictionary<Type, JsonConverter> Converters { get; set; }
public CustomResolver(Dictionary<Type, JsonConverter> converters)
{
Converters = converters;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
if (Converters.TryGetValue(objectType, out JsonConverter converter))
{
contract.Converter = converter;
}
return contract;
}
}
Then, when you wanted to use the AlternativeConverter in place of the BaseJsonConverter, you could use the custom resolver like this:
// map the `Author` type to the `AlternativeConverter`
var converters = new Dictionary<Type, JsonConverter>()
{
{ typeof(Author), new AlternativeConverter() }
};
// Create a resolver with the converter mapping and add it to the serializer settings
var settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver(converters)
};
// Use the settings when deserializing
var author = JsonConvert.DeserializeObject<Author>(jsonString, settings);
Demo Fiddle: https://dotnetfiddle.net/cu0igV
Of course, if all you're really doing with these converters is remapping properties to different names, you could just use a ContractResolver for that in the first place and get rid of the converters altogether. See Json.NET deserialize or serialize json string and map properties to different property names defined at runtime for more information on that approach.
I think you should try to use different JsonSerializerSettings instances for different data sources, with different Converters collections. And remove JsonConverter attributes from your classes.

How to customize value setting during deserialization with contract resolver and value provider

I am failing at a task to encrypt certain fields when serializing into JSON and decrypt those fields during deserialization into a specific C# class.
I reduced the problem to its most basic issue, which is that I cannot customize the deserialization of specific fields by manipulating the value, and I don't know the reason. I am using a custom contract resolver and a custom value provider for each field. I can see that the GetValue function is executed but the SetValue is never executed.
The code sample:
class Program
{
static void Main(string[] args)
{
var text = "This is text";
var number = 1;
var anotherText = "This is another text";
var anotherNumber = 2;
var sampleInner = new SampleInner(anotherText, anotherNumber);
var sample = new SampleMessage(text, number, sampleInner);
var myCustomContractResolver = new MyCustomContractResolver();
var jsonSettings = GetJsonSettings(myCustomContractResolver);
Console.WriteLine("Serializing..");
var json = JsonConvert.SerializeObject(sample, jsonSettings);
Console.WriteLine(json);
Console.WriteLine("Deserializing..");
var sampleDeserialized = JsonConvert.DeserializeObject(json, typeof(SampleMessage), jsonSettings);
Console.WriteLine(sampleDeserialized);
Console.ReadLine();
}
private static JsonSerializerSettings GetJsonSettings(IContractResolver contractResolver)
{
var jsonSettings =
new JsonSerializerSettings
{
ContractResolver = contractResolver
};
return jsonSettings;
}
}
the custom contract resolver:
public class MyCustomContractResolver
: DefaultContractResolver
{
public MyCustomContractResolver()
{
NamingStrategy = new CamelCaseNamingStrategy();
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var jsonProperties = base.CreateProperties(type, memberSerialization);
foreach (var jsonProperty in jsonProperties)
{
var propertyInfo = type.GetProperty(jsonProperty.UnderlyingName);
var defaultValueProvider = jsonProperty.ValueProvider;
jsonProperty.ValueProvider = new MyValueProvider(defaultValueProvider);
}
return jsonProperties;
}
}
and the custom value provider whose SetValue is never executed during deserialization:
public class MyValueProvider
: IValueProvider
{
private readonly IValueProvider _valueProvider;
public MyValueProvider(IValueProvider valueProvider)
{
_valueProvider = valueProvider;
}
public void SetValue(object target, object value)
{
//This is not executed during deserialization. Why?
_valueProvider.SetValue(target, value);
Console.WriteLine($"Value set: {value}");
}
public object GetValue(object target)
{
var value = _valueProvider.GetValue(target);
Console.WriteLine($"Value get: {value}");
return value;
}
}
Here is the sample code to reproduce it in case it's needed.
Hopefully somebody can let me know what am I missing :)
UPDATE 1: The object I serialize/deserialize is immutable (no public setters) and that's a requirement because I need to support such objects. As a comment points out, then it makes sense there is no SetValue executed
UPDATE 2: Thanks to #dbc for the brilliant answer no I know a good workaround for deserialization into immutable object. The final version code after the accepted answer.
UPDATE 3: The chosen answer is absolutely correct given the question. However, after investigating further I decided to go with a slightly different approach that works for both immutable and mutable classes in case somebody is in a similar situation. Instead using value providers I use now a combination of contract resolver and json converter so that with the contract resolver I can decide how to serialize/deserialize based on the type, and with the json converter I can access the value during serialization/deserialization and manipulate as desired.
Basically, on my contract resolver I override the method to create properties (where I can access my original Type properties) and selectively I specify which json converter to use.
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var jsonProperties = base.CreateProperties(type, memberSerialization);
//Filter here based on type, attribute or whatever and if want to customize a specific property type:
foreach (var jsonProperty in jsonProperties)
{
jsonProperty.Converter = new MyJsonConverter();
}
return jsonProperties;
}
and in the MyJsonConverter we can choose what to do when writing into json or when reading from json:
public class MyJsonConverter
: JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//I can do whatever with value
var finalValue = $"{value}-edited";
writer.WriteValue(finalValue);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// I can do whatever with the value
var value = (string)reader.Value;
var newValue = "whatever";
return newValue;
}
public override bool CanWrite => true;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return true;
}
}
The reason that IValueProvider.SetValue is not called for the properties of SampleInner is that SampleInner is immutable and so there are no set methods to be called. Instead, the JSON properties are matched to arguments of the type's single parameterized constructor by name (modulo case), deserialized to the type of the matched argument, and then passed into the constructor as explained here.
Even if you were to make the properties mutable the setter will not be called for properties already passed into the constructor, as Json.NET makes the (reasonable) assumption that passing a property value into the constructor is sufficient to set the property's value.
So, what are your options?
Firstly, you could make your types mutable with a default constructor. The setters and constructor could be private as long as they are marked with appropriate attributes:
public class SampleInner
{
[JsonProperty] // Adding this attribute informs Json.NET that the private setter can be called.
public string AnotherText { get; private set; }
[JsonProperty]
public int AnotherNumber { get; private set; }
[JsonConstructor] // Adding this attribute informs Json.NET that this private constructor can be called
private SampleInner() { }
public SampleInner(string anotherText, int anotherNumber)
{
this.AnotherText = anotherText;
this.AnotherNumber = anotherNumber;
}
}
Now that there are setters to be called, your MyValueProvider.SetValue() will get invoked. Demo fiddle #1 here.
Secondly, if you cannot so modify your types, you could wrap the constructor method called in some decorator that does the necessary pre-processing, however this is made difficult by the fact that JsonObjectContract.ParameterizedCreator is nonpublic. Thus you cannot get direct access to the parameterized constructor that Json.NET has chosen, in order to decorate it. You can, however, determine its arguments, which are specified by JsonObjectContract.CreatorParameters. When this collection is populated then either OverrideCreator is set or the (secret) ParameterizedCreator is set. This allows the necessary logic to be inserted as follows:
public class MyCustomContractResolver : DefaultContractResolver
{
public MyCustomContractResolver() { NamingStrategy = new CamelCaseNamingStrategy(); }
static ObjectConstructor<Object> GetParameterizedConstructor(JsonObjectContract contract)
{
if (contract.OverrideCreator != null)
return contract.OverrideCreator;
// Here we assume that JsonSerializerSettings.ConstructorHandling == ConstructorHandling.Default
// If you would prefer AllowNonPublicDefaultConstructor then you need to remove the check on contract.DefaultCreatorNonPublic
if (contract.CreatorParameters.Count > 0 && (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic))
{
// OK, Json.NET has a parameterized constructor stashed away in JsonObjectContract.ParameterizedCreator
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs#L100
// But, annoyingly, this value is internal so we cannot get it!
// But because CreatorParameters.Count > 0 and OverrideCreator == null we can infer that such a constructor exists, and so call it using Activator.CreateInstance
return (args) => Activator.CreateInstance(contract.CreatedType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, args, CultureInfo.InvariantCulture);
}
return null;
}
static ObjectConstructor<Object> CustomizeConstructor(JsonObjectContract contract, ObjectConstructor<Object> constructor)
{
if (constructor == null)
return null;
return (args) =>
{
// Add here your customization logic.
// You can match creator parameters to properties by property name if needed.
foreach (var pair in args.Zip(contract.CreatorParameters, (a, p) => new { Value = a, Parameter = p }))
{
// Get the corresponding property in case you need to, e.g., check its attributes:
var property = contract.Properties[pair.Parameter.PropertyName];
if (property == null)
Console.WriteLine("Argument {0}: Value {1}", pair.Parameter.PropertyName, pair.Value);
else
Console.WriteLine("Argument {0} (corresponding to JsonProperty {1}): Value {2}", pair.Parameter.PropertyName, property, pair.Value);
}
return constructor(args);
};
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.OverrideCreator = CustomizeConstructor(contract, GetParameterizedConstructor(contract));
return contract;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var jsonProperties = base.CreateProperties(type, memberSerialization);
foreach (var jsonProperty in jsonProperties)
{
var defaultValueProvider = jsonProperty.ValueProvider;
jsonProperty.ValueProvider = new MyValueProvider(defaultValueProvider);
}
return jsonProperties;
}
}
Notes:
If a default constructor exists but is nonpublic, the above contract resolver assumes that it is not used. If you would prefer nonpublic default constructors to be used, you will need to set JsonSerializerSettings.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor and also modify the code in GetParameterizedConstructor() above to remove the check on contract.DefaultCreatorNonPublic:
if (contract.CreatorParameters.Count > 0 && contract.DefaultCreator == null)
It would be reasonable to request an enhancement to allow access to, and customization of, JsonObjectContract.ParameterizedCreator.
(I suppose you could try to access JsonObjectContract.ParameterizedCreator directly via reflection...)
Demo fiddle #2 here.

Is it possible to deserialize into property rather than an object

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
}
}

NewtonSoft JsonConverter - Access other properties

I have a need to format the output json of a decimal to a currency, with the culture specified my the object I am serializing, the object could be nested so I cannot preset the option in the serializer. The current way I am doing this is by using extra string properties that format the output.
[JsonIgnore]
public decimal Cost {get;set;}
[JsonIgnore]
public CultureInfo Culture {get;set;}
public string AsCurrency(decimal value) {
return string.Format(this.Culture, "{0:c}", value);
}
[JsonProperty("FormattedCost")]
public string FormatedCost {
get { return this.AsCurrency(this.Cost); }
}
I have alot of properties to deal with, I'm not bothered about Deserializing, the JsonObject is used by a different language to populated a PDF and so I want the string values.
Ideally I'd like a JsonConverter so I can just do
[JsonProperty("FormattedCost")]
[JsonConverter(typeof(MyCurrencyConverter))]
public decimal Cost {get;set;}
The issue I have is how to access the Culture property of the containing object in the converter.
public class MyCurrencyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var culture = // How do I get the Culture from the parent object?
writer.WriteValue(string.format(culture, "{0:c}", (decimal)value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(decimal) == objectType;
}
}
As Requested sample JSON.
for an array of Contract classes that each have a Cost and an Culture.
[{ FormattedCost : "£5000.00"}, { FormattedCost : "$8000.00"}, { FormattedCost : "€599.00"}]
The actual objects are a lot more complicated, multiple fields with nested Assets that would have their own figures. Additionally not all decimals would be currencies.
I don't really want to have to write a custom serializer for the Contract itself as I would then have to modify it each time the properties change.
The ideal solution is being able to tag certain decimal properties with the converter attribute so it can handle it.
The other way I was thinking of going was to make a custom class for the decimal properties with an implicit conversion from decimal, however that gets more complicated as some properties are calculated properties based on previous results.
WORKAROUND
I have a work-around for my use case, but it uses reflection to obtain a private variable in the serializer.
var binding = BindingFlags.NonPublic | BindingFlags.Instance;
var writer = serializer.GetType()
.GetMethod("GetInternalSerializer", binding)
?.Invoke(serializer, null);
var parent = writer?.GetType()
.GetField("_serializeStack", binding)
?.GetValue(writer) is List<object> stack
&& stack.Count > 1 ? stack[stack.Count - 2] as MyType: null;
In my tested use cases this gives me the parent object, but it's not using the public API.
What you want to do is to intercept and modify the value of a specific property of an object as it is being serialized while using default serialization for all other properties. This can be done with a custom ContractResolver that replaces the ValueProvider of the property in question when a specific attribute is applied.
First, define the following attribute and contract resolver:
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false)]
public class JsonFormatAttribute : System.Attribute
{
public JsonFormatAttribute(string formattingString)
{
this.FormattingString = formattingString;
}
/// <summary>
/// The format string to pass to string.Format()
/// </summary>
public string FormattingString { get; set; }
/// <summary>
/// The name of the underlying property that returns the object's culture, or NULL if not applicable.
/// </summary>
public string CulturePropertyName { get; set; }
}
public class FormattedPropertyContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return base.CreateProperties(type, memberSerialization)
.AddFormatting();
}
}
public static class JsonContractExtensions
{
class FormattedValueProvider : IValueProvider
{
readonly IValueProvider baseProvider;
readonly string formatString;
readonly IValueProvider cultureValueProvider;
public FormattedValueProvider(IValueProvider baseProvider, string formatString, IValueProvider cultureValueProvider)
{
this.baseProvider = baseProvider;
this.formatString = formatString;
this.cultureValueProvider = cultureValueProvider;
}
#region IValueProvider Members
public object GetValue(object target)
{
var value = baseProvider.GetValue(target);
var culture = cultureValueProvider == null ? null : (CultureInfo)cultureValueProvider.GetValue(target);
return string.Format(culture ?? CultureInfo.InvariantCulture, formatString, value);
}
public void SetValue(object target, object value)
{
// This contract resolver should only be used for serialization, not deserialization, so throw an exception.
throw new NotImplementedException();
}
#endregion
}
public static IList<JsonProperty> AddFormatting(this IList<JsonProperty> properties)
{
ILookup<string, JsonProperty> lookup = null;
foreach (var jsonProperty in properties)
{
var attr = (JsonFormatAttribute)jsonProperty.AttributeProvider.GetAttributes(typeof(JsonFormatAttribute), false).SingleOrDefault();
if (attr != null)
{
IValueProvider cultureValueProvider = null;
if (attr.CulturePropertyName != null)
{
if (lookup == null)
lookup = properties.ToLookup(p => p.UnderlyingName);
var cultureProperty = lookup[attr.CulturePropertyName].FirstOrDefault();
if (cultureProperty != null)
cultureValueProvider = cultureProperty.ValueProvider;
}
jsonProperty.ValueProvider = new FormattedValueProvider(jsonProperty.ValueProvider, attr.FormattingString, cultureValueProvider);
jsonProperty.PropertyType = typeof(string);
}
}
return properties;
}
}
Next, define your object as follows:
public class RootObject
{
[JsonFormat("{0:c}", CulturePropertyName = nameof(Culture))]
public decimal Cost { get; set; }
[JsonIgnore]
public CultureInfo Culture { get; set; }
public string SomeValue { get; set; }
public string SomeOtherValue { get; set; }
}
Finally, serialize as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new FormattedPropertyContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy(),
},
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
Notes:
Since you are not serializing the culture name, I can't see any way to deserialize the Cost property. Thus I threw an exception from the SetValue method.
(And, even if you were serializing the culture name, since a JSON object is an unordered set of name/value pairs according the standard, there's no way to guarantee the culture name appears before the cost in the JSON being deserialized. This may be related to why Newtonsoft does not provide access to the parent stack. During deserialization there's no guarantee that required properties in the parent hierarchy have been read - or even that the parents have been constructed.)
If you have to apply several different customization rules to your contracts, consider using ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net.
You may want to cache the contract resolver for best performance.
Another approach would be to add a converter to the parent object that generates a default serialization to JObject by disabling itself temporarily, tweaks the returned JObject, then writes that out. For examples of this approach see JSON.Net throws StackOverflowException when using [JsonConvert()] or Can I serialize nested properties to my class in one operation with Json.net?.
In comments you write, Inside WriteJson I cannot figure out how to access the parent object and it's properties. It should be possible to do this with a custom IValueProvider that returns a Tuple or similar class containing the parent and the value, which would be used in concert with a specific JsonConverter that expects such input. Not sure I'd recommend this though since it's extremely tricky.
Working sample .Net fiddle.

Serialize Dynamic Property Name for an Object using JSON.NET

I'm using JSON.NET for serialization of my objects for connecting to a REST API. One of the properties in my object that needs to be serialized to JSON has a dynamic property name.
If the value contained in the struct for this property is a numeric value, then the JSON property is "type_id", however if this value is a string value, then the JSON property name is "type_code". I attempted to use a custom JsonConverter for this, but I get a JsonWriterException with this message when I attempt to serialize:
"Token PropertyName in state Property would result in an invalid JSON object. Path ''."
Below is a subset of my object, as seen below I didn't specify a property name in my object for that as such:
[JsonProperty("title",Required=Required.Always,Order=1)]
public string Title { get; set; }
[JsonProperty("date",Order=3)]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime Date { get; set; }
[JsonProperty(Order=2)]
[JsonConverter(typeof(TypeIdentifierJsonConverter))]
public TypeIdentifier DocTypeIdentifier { get; set; }
In the TypeIdentifier class I have the following in my WriteJson() method:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
TypeIdentifier docTypeId;
id= (TypeIdentifier) value;
writer.WritePropertyName(id.ParameterName);
writer.WriteValue(id.Value);
}
However, I am assuming it's defaulting to the name of the object's property instead of my custom one, causing two property names for a single value within the JSON string. How can the property name be set dynamically for this, since the JsonPropertyAttribute tag appears to pull the object's property name when not specified explicitly?
NOTE: This object will never need to be deserialized from this app.
EDIT: This object is tagged with the [JsonObject(MemberSerialization.OptIn)] attribute
A JsonConverter cannot set the name of a property in a parent object. When the converter's WriteJson method is called, the property name has already been written to the JSON; the writer is expecting only a value that point. That is why you are getting an error. In order to make this work, the custom converter would have to be made for the parent object. That converter would then be responsible for writing the property names and values of its children.
Follow-up
It is possible to write a converter for the parent object such that the JSON attributes applied to it are still respected, while still achieving the result you want. I'll outline the approach below.
First, a little bit of setup. Since you did not say what your class was called, I'll assume for this example that it is called Document. We only need to make one substantive change to it, and that is to remove the [JsonConverter] attribute from the DocTypeIdentifier property. So we have:
[JsonObject(MemberSerialization.OptIn)]
class Document
{
[JsonProperty("title", Required = Required.Always, Order = 1)]
public string Title { get; set; }
[JsonProperty("date", Order = 3)]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime Date { get; set; }
[JsonProperty(Order = 2)]
public TypeIdentifier DocTypeIdentifier { get; set; }
public string OtherStuff { get; set; }
}
You also did not show the code for the TypeIdentifier class, so I'll just assume it looks like this, for sake of example:
class TypeIdentifier
{
public string Value { get; set; }
public string ParameterName { get; set; }
}
With that out of the way, we can make the converter. The approach is fairly straightforward: we load the Document into a JObject, taking advantage of the fact that it respects the attributes applied, then go back and fix the serialization of the DocTypeIdentifier since it needs special handling. Once we have that, we write out the JObject to the JsonWriter. Here is the code:
class DocumentConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Document));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Document doc = (Document)value;
// Create a JObject from the document, respecting existing JSON attribs
JObject jo = JObject.FromObject(value);
// At this point the DocTypeIdentifier is not serialized correctly.
// Fix it by replacing the property with the correct name and value.
JProperty prop = jo.Children<JProperty>()
.Where(p => p.Name == "DocTypeIdentifier")
.First();
prop.AddAfterSelf(new JProperty(doc.DocTypeIdentifier.ParameterName,
doc.DocTypeIdentifier.Value));
prop.Remove();
// Write out the JSON
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now we have the converter, but the catch is we cannot simply decorate the Document class with a [JsonConverter] attribute in order to use it. If we did, we would end up with a recursive loop as the converter tried to use itself when we loaded the document into the JObject. So instead, we need to create an instance of the converter and pass it to the serializer via settings. The converter's CanConvert method ensures it gets used on the correct class. The JObject.FromObject method uses a different serializer instance internally, so it does not see the DocumentConverter and thus does not get into trouble.
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new DocumentConverter());
string json = JsonConvert.SerializeObject(doc, settings);
Here is a demo showing the converter in action:
class Program
{
static void Main(string[] args)
{
Document doc = new Document
{
Title = "How to write a JSON converter",
Date = DateTime.Today,
DocTypeIdentifier = new TypeIdentifier
{
ParameterName = "type_id",
Value = "26"
},
OtherStuff = "this should not appear in the JSON"
};
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new DocumentConverter());
settings.Formatting = Formatting.Indented;
string json = JsonConvert.SerializeObject(doc, settings);
Console.WriteLine(json);
}
}
Here is the output from the above:
{
"title": "How to write a JSON converter",
"type_id": "26",
"date": "2014-03-28T00:00:00-05:00"
}

Categories

Resources