In the new System.Text.Json; namespace is there something like IContractResolver i am trying to migrate my project away from Newtonsoft.
This is one of the classes i am trying to move:
public class SelectiveSerializer : DefaultContractResolver
{
private readonly string[] fields;
public SelectiveSerializer(string fields)
{
var fieldColl = fields.Split(',');
this.fields = fieldColl
.Select(f => f.ToLower().Trim())
.ToArray();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = o => fields.Contains(member.Name.ToLower());
return property;
}
}
The equivalent types in System.Text.Json -- JsonClassInfo and JsonPropertyInfo -- are internal. There is an open enhancement
Equivalent of DefaultContractResolver in System.Text.Json #31257
asking for a public equivalent. – dbc Nov 25 at 19:11
Github issues:
Open up metadata infrastructure of System.Text.Json #34456
Equivalent of DefaultContractResolver.CreateProperties override in System.Text.Json #60518
Equivalent of DefaultContractResolver in System.Text.Json #31257
Please try this:
I wrote this as an extension to System.Text.Json to offer missing features: https://github.com/dahomey-technologies/Dahomey.Json.
You will find support for programmatic object mapping.
Define your own implementation of IObjectMappingConvention:
public class SelectiveSerializer : IObjectMappingConvention
{
private readonly IObjectMappingConvention defaultObjectMappingConvention = new DefaultObjectMappingConvention();
private readonly string[] fields;
public SelectiveSerializer(string fields)
{
var fieldColl = fields.Split(',');
this.fields = fieldColl
.Select(f => f.ToLower().Trim())
.ToArray();
}
public void Apply<T>(JsonSerializerOptions options, ObjectMapping<T> objectMapping) where T : class
{
defaultObjectMappingConvention.Apply<T>(options, objectMapping);
foreach (IMemberMapping memberMapping in objectMapping.MemberMappings)
{
if (memberMapping is MemberMapping<T> member)
{
member.SetShouldSerializeMethod(o => fields.Contains(member.MemberName.ToLower()));
}
}
}
}
Define your class:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
Setup json extensions by calling on JsonSerializerOptions the extension method SetupExtensions defined in the namespace Dahomey.Json:
JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
Register the new object mapping convention for the class:
options.GetObjectMappingConventionRegistry().RegisterConvention(
typeof(Employee), new SelectiveSerializer("FirstName,Email,Id"));
Then serialize your class with the regular Sytem.Text.Json API:
Employee employee = new Employee
{
Id = 12,
FirstName = "John",
LastName = "Doe",
Email = "john.doe#acme.com"
};
string json = JsonSerializer.Serialize(employee, options);
// {"Id":12,"FirstName":"John","Email":"john.doe#acme.com"};
Contract customization will be implemented in .NET 7, and is available in Preview 6.
From the documentation page What’s new in System.Text.Json in .NET 7: Contract Customization by Eirik Tsarpalis, Krzysztof Wicher and Layomi Akinrinade:
The contract metadata for a given type T is represented using JsonTypeInfo<T>, which in previous versions served as an opaque token used exclusively in source generator APIs. Starting in .NET 7, most facets of the JsonTypeInfo contract metadata have been exposed and made user-modifiable. Contract customization allows users to write their own JSON contract resolution logic using implementations of the IJsonTypeInfoResolver interface:
public interface IJsonTypeInfoResolver
{
JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options);
}
A contract resolver returns a configured JsonTypeInfo instance for the given Type and JsonSerializerOptions combination. It can return null if the resolver does not support metadata for the specified input type.
Contract resolution performed by the default, reflection-based serializer is now exposed via the DefaultJsonTypeInfoResolver class, which implements IJsonTypeInfoResolver.
Starting from .NET 7 the JsonSerializerContext class used in source generation also implements IJsonTypeInfoResolver.
You can create your own IJsonTypeInfoResolver via one of the following methods:
You can subclass DefaultJsonTypeInfoResolver and override GetTypeInfo(Type, JsonSerializerOptions). This resembles overriding Json.NET's DefaultContractResolver.CreateContract().
You can add an Action<JsonTypeInfo> to DefaultJsonTypeInfoResolver.Modifiers to modify the default JsonTypeInfo generated for selected types after creation.
Combining multiple customizations looks easier with this approach than with the inheritance approach. However, since the modifier actions are applied in order, there is a chance that later modifiers could conflict with earlier modifiers.
You could create your own IJsonTypeInfoResolver from scratch that creates contracts only for those types that interest you, and combine it with some other type info resolver via JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]).
JsonTypeInfoResolver.Combine() is also useful when you want to use compile-time generated JsonSerializerContext instances with a runtime contract resolver that customizes serialization for certain types only.
Once you have a custom resolver, you can set it via JsonSerializerOptions.TypeInfoResolver.
Thus your SelectiveSerializer can be converted to a DefaultJsonTypeInfoResolver roughly as follows, using modifiers. First define the following fluent extension methods:
public static partial class JsonSerializerExtensions
{
public static DefaultJsonTypeInfoResolver SerializeSelectedFields(this DefaultJsonTypeInfoResolver resolver, string fields) =>
SerializeSelectedFields(resolver, fields?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? throw new ArgumentNullException(nameof(fields)));
public static DefaultJsonTypeInfoResolver SerializeSelectedFields(this DefaultJsonTypeInfoResolver resolver, IEnumerable<string> membersToSerialize)
{
if (resolver == null)
throw new ArgumentNullException(nameof(resolver));
if (membersToSerialize == null)
throw new ArgumentNullException(nameof(membersToSerialize));
var membersToSerializeSet = membersToSerialize.ToHashSet(StringComparer.OrdinalIgnoreCase); // Possibly this should be changed to StringComparer.Ordinal
resolver.Modifiers.Add(typeInfo =>
{
if (typeInfo.Kind == JsonTypeInfoKind.Object)
{
foreach (var property in typeInfo.Properties)
{
if (property.GetMemberName() is {} name && !membersToSerializeSet.Contains(name))
property.ShouldSerialize = static (obj, value) => false;
}
}
});
return resolver;
}
public static string? GetMemberName(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.Name;
}
And now you can set up your JsonSerializerOptions e.g. as follows:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
.SerializeSelectedFields("FirstName,Email,Id"),
// Add other options as required
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
};
Notes:
JsonPropertyInfo.ShouldSerialize (also new in .NET 7) can be used for conditional serialization of properties.
When a JsonPropertyInfo was created by the reflection or source-gen resolvers, JsonPropertyInfo.AttributeProvider will be the underlying PropertyInfo or FieldInfo.
For confirmation see this comment by layomia to System.Text.Json: In .NET 7, how can I determine the JsonPropertyInfo created for a specific member, so I can customize the serialization of that member? #77761.
All serialization metadata should be constructed using locale-invariant string logic. In your code you use ToLower() but it would have been better to use ToLowerInvariant(). In my modifier action I use StringComparer.OrdinalIgnoreCase which avoids the need to lowercase the strings.
System.Text.Json is case-sensitive by default so you might want to use case-sensitive property name matching when filtering selected fields.
Related
I want to deserialize a JSON file into a List<Person> and I want to intercept the instances of Person that gets created (not the instance of List<Person> but this might be helpful too) is it possible to do it by implementing a custom IJsonTypeInfoResolver or deriving from DefaultJsonTypeInfoResolver?
I've tried to derive from DefaultJsonTypeInfoResolver and create a custom resolver by overriding GetTypeInfo but I'm not sure how to get the created instances of Person.
I've checked some of the properties and methods on JsonTypeInfo through debugging but I couldn't really understand what I should do or how I should use the API and the documentation seems lacking for my specific case, unfortunately.
Here is an example:
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
TypeInfoResolver = new JsonPersonResolver()
};
options.Converters.Add(new JsonDateTimeConverter());
options.Converters.Add(new JsonDateOnlyConverter());
options.Converters.Add(new JsonColorConverter());
options.Converters.Add(new JsonDecimalConverter());
var people = await JsonSerializer.DeserializeAsync<List<Person>>(stream, options);
sealed record Person
{
public string FullName { get; set; }
public int Age { get; set; }
}
sealed class JsonCustomResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var info = base.GetTypeInfo(type, options);
// Do what?
return info;
}
}
You can get a callback every time an instance of Person is created by adding a DefaultJsonTypeInfoResolver modifier that invokes a custom OnDeserializing or OnDeserialized callback for the Person type.
First, define the following extension methods for DefaultJsonTypeInfoResolver:
public static class JsonExtensions
{
public static DefaultJsonTypeInfoResolver AddOnDeserializing<T>(this DefaultJsonTypeInfoResolver resolver, Action<T> onDeserializing)
{
resolver.Modifiers.Add(typeInfo =>
{
if (typeof(T) == typeInfo.Type) // Or typeof(T).IsAssignableFrom(typeinfo.Type) if you prefer
{
if (typeInfo.OnDeserializing == null)
typeInfo.OnDeserializing = (o) => onDeserializing((T)o);
else
{
var old = typeInfo.OnDeserializing;
typeInfo.OnDeserializing = (o) => { old(o); onDeserializing((T)o); };
}
}
});
return resolver;
}
public static DefaultJsonTypeInfoResolver AddOnDeserialized<T>(this DefaultJsonTypeInfoResolver resolver, Action<T> onDeserialized)
{
resolver.Modifiers.Add(typeInfo =>
{
if (typeof(T) == typeInfo.Type) // Or typeof(T).IsAssignableFrom(typeinfo.Type) if you prefer
{
if (typeInfo.OnDeserialized == null)
typeInfo.OnDeserialized = (o) => onDeserialized((T)o);
else
{
var old = typeInfo.OnDeserialized;
typeInfo.OnDeserialized = (o) => { old(o); onDeserialized((T)o); };
}
}
});
return resolver;
}
}
Now you will be able to capture the creation of all Person objects wherever they are created in your deserialized object graph by adding an appropriate Action<Person>:
List<Person> allPeopleCreatedAnywhere = new();
Action<Person> personCreated = p => { Console.WriteLine("Intercepted persion {0}", p); allPeopleCreatedAnywhere.Add(p); };
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
// Use AddOnDeserializing if you want to intercept the Person before it is populated
// Use AddOnDeserialized if you want to intercept the Person after it is populated.
.AddOnDeserialized(personCreated)
// Add other modifiers as required
,
// Add other settings and converters as required
PropertyNameCaseInsensitive = true,
};
var people = await JsonSerializer.DeserializeAsync<List<Person>>(stream, options);
Console.WriteLine("A total of {0} people were intercepted.", allPeopleCreatedAnywhere.Count);
Notes:
As explained in the docs:
Serialization callbacks are only supported for Object metadata.
Thus you will only be able to get intercept instances of c# types that are serialized as JSON objects. If you try to intercept instances of types serialized as JSON arrays such as List<Person> or primitives such as DateTime with this technique, System.Text.Json will throw an exception.
I recommended this approach because it doesn't require a converter, and it works with objects that have parameterized constructors. An alternative might be to chain something to JsonTypeInfo.CreateObject, however that wouldn't work with parameterized constructors.
Demo fiddle here.
Fellow programmers,
I've encountered a strange behavior in Newtonsoft.Json.
When I'm trying to serialize an object looking like this:
public class DMSDocWorkflowI
{
[JsonProperty("DMSDocWorkflowIResult")]
public bool DMSDocWorkflowIResult { get; set; }
[JsonProperty("DMSDocWorkflowIResultSpecified")]
public bool DMSDocWorkflowIResultSpecified { get; set; }
}
Using this simple call with no custom converters / binders / contract resolvers:
var testObject = new DMSDocWorkflowI();
var json = JsonConvert.SerializeObject(testObject, Formatting.Indented);
or even with JToken.FromObject(...) I always get only one property:
{
"DMSDocWorkflowIResultSpecified": false
}
When I attach the trace writer, it catches only this:
[0]: "2016-08-30T11:06:27.779 Info Started serializing *****DMSDocWorkflowI. Path ''."
[1]: "2016-08-30T11:06:27.779 Verbose IsSpecified result for property 'DMSDocWorkflowIResult' on *****DMSDocWorkflowI: False. Path ''."
[2]: "2016-08-30T11:06:27.779 Info Finished serializing *****.DMSDocWorkflowI. Path ''."
[3]: "2016-08-30T11:06:27.780 Verbose Serialized JSON: \r\n{\r\n \"DMSDocWorkflowIResultSpecified\": false\r\n}"
So it seems Newtonsoft.Json treats this "Specified" property somewhat magically.
Can I turn this off?
I need both these properties in resulting JSON with exactly these names.
This behavior is mentioned, very briefly, in the Json.NET 4.0.1 release notes: New feature - Added XmlSerializer style Specified property support. The XmlSerializer functionality is in turn described in MinOccurs Attribute Binding Support:
[For optional fields] Xsd.exe generates a public field of type bool whose name is the element field's name with Specified appended. For example, if the element field's name is startDate, the bool field's name becomes startDateSpecified. When serializing an object to XML, the XmlSerializer class checks the value of the bool field to determine whether to write the element.
I feel as though this functionality should be documented here, but is not. You might want to open a documentation issue with Newtonsoft.
Since you don't want this behavior, if you are using Json.NET 11.0.1 or later, you can disable it for all classes by instantiating your own DefaultContractResolver and settting DefaultContractResolver.IgnoreIsSpecifiedMembers = true:
public static class JsonContractResolvers
{
// Newtonsoft recommends caching and reusing contract resolvers for best performance:
// https://www.newtonsoft.com/json/help/html/Performance.htm#ReuseContractResolver
// But be sure not to modify IgnoreIsSpecifiedMembers after the contract resolver is first used to generate a contract.
public static readonly DefaultContractResolver IgnoreIsSpecifiedMembersResolver =
new DefaultContractResolver { IgnoreIsSpecifiedMembers = true };
}
Then pass it to JsonConvert as follows:
var settings = new JsonSerializerSettings { ContractResolver = JsonContractResolvers.IgnoreIsSpecifiedMembersResolver };
var json = JsonConvert.SerializeObject(testObject, Formatting.Indented, settings);
Or to create a JToken do:
var jToken = JToken.FromObject(testObject, JsonSerializer.CreateDefault(settings));
If you are using an earlier version, you will need to create and cache a custom contract resolver:
public static class JsonContractResolvers
{
public static readonly DefaultContractResolver IgnoreIsSpecifiedMembersResolver =
new IgnoreSpecifiedContractResolver();
}
internal class IgnoreSpecifiedContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.GetIsSpecified = null;
property.SetIsSpecified = null;
return property;
}
}
I have an observable collection that contains a list of products that is binded to ListView.
However I want to export this Observable Collection as a JSON file AND only specific entries so I can submit it through the API.
For example.
The full observable collection contains
Product ID
Product Name
Product Price
Product Qty
But I want to extract the JSON file to only:
Product ID
Product Qty
Here's my code:
public static ObservableCollection<FBProduct> fbproducts = new ObservableCollection<FBProduct>();
Here's my JSON deserialiser
shoppingcartjson = JsonConvert.SerializeObject(ShoppingCart.fbproducts);
How can I only extract only ProductID and ProductQTY from that ObservableCollection like so:
"line_items": [{"product_id":79631,"quantity":1}],
It's simple in your FBProduct class use the JsonIgnore attribute!
For instance:
public class FBProduct
{
[JsonIgnore]
public double Name { get; set; }
.
.
Also, add the following using statement:
using Newtonsoft.Json;
Good luck!
Feel free to get back if you have questions.
If you want to always ignore a property when serialising your FBProduct, then go ahead and use FreakyAli's answer. I'll give a quick explainer as to how to ignore properties only sometimes.
Sometimes you want to ignore some properties, while other times you want the full class without ignoring any properties. But by placing a [JsonIgnore] attribute on a property you will ignore it always, which isn't great. So instead, Newtonsoft offers a way to ignore properties conditionally, using what they call a contract resolver. You can implement your own contract resolvers to be able to programmatically ignore properties sometimes (as well as do everything else you could using their attributes).
Here is how you would go about implementing a contract resolver that conditionally ignores some properties:
public class IgnorePropertyContractResolver : DefaultContractResolver
{
// Holds our information for which properties to ignore on which types
private readonly Dictionary<Type, HashSet<string>> _ignores;
public IgnorePropertyContractResolver()
{
_ignores = new Dictionary<Type, HashSet<string>>();
}
public void IgnoreProperty(Type type, params string[] propertyNames)
{
// If we don't know the type to ignore properties on, initialize the HashSet
if (_ignores.ContainsKey(type))
_ignores[type] = new HashSet<string>();
foreach (var prop in propertyNames)
_ignores[type].Add(prop);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
// Create the property as normal
var property = base.CreateProperty(member, memberSerialization);
// If we're ignoring the property
if (IsIgnored(property.DeclaringType, property.PropertyName))
{
// Don't serialize and ignore
property.ShouldSerialize = i => false;
property.Ignored = true;
}
return property;
}
private bool IsIgnored(Type type, string propertyName)
{
// If we're not ignoring any property on the type, return false
if (!_ignores.ContainsKey(type))
return false;
// If we are ignoring some properties on the type, return if we're ignoring the given property
return _ignores[type].Contains(propertyName);
}
}
We then use this custom contract resolver as follwing:
var fbProduct = new FBProduct();
var resolver = new IgnorePropertyContractResolver();
resolver.IgnoreProperty(typeof(FBProduct),
nameof(FBProduct.ProductID),
nameof(FBProduct.Name),
nameof(FBProduct.Price),
nameof(FBProduct.Qty)
);
var serialized = JsonConvert.SerializeObject(
fbProduct,
Formatting.None, // You can choose any formatting you want
new JsonSerializerSettings
{
ContractResolver = resolver
}
);
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.
I have a Web Api project being configured like this:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
However, I want dictionary keys casing to remain unchanged. is there any attribute in Newtonsoft.Json I can use to a class to denote that I want casing to remain unchanged during serialization?
public class SomeViewModel
{
public Dictionary<string, string> Data { get; set; }
}
There is not an attribute to do this, but you can do it by customizing the resolver.
I see that you are already using a CamelCasePropertyNamesContractResolver. If you derive a new resolver class from that and override the CreateDictionaryContract() method, you can provide a substitute DictionaryKeyResolver function that does not change the key names.
Here is the code you would need:
class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
contract.DictionaryKeyResolver = propertyName => propertyName;
return contract;
}
}
Demo:
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo
{
AnIntegerProperty = 42,
HTMLString = "<html></html>",
Dictionary = new Dictionary<string, string>
{
{ "WHIZbang", "1" },
{ "FOO", "2" },
{ "Bar", "3" },
}
};
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
Formatting = Formatting.Indented
};
string json = JsonConvert.SerializeObject(foo, settings);
Console.WriteLine(json);
}
}
class Foo
{
public int AnIntegerProperty { get; set; }
public string HTMLString { get; set; }
public Dictionary<string, string> Dictionary { get; set; }
}
Here is the output from the above. Notice that all of the class property names are camel-cased, but the dictionary keys have retained their original casing.
{
"anIntegerProperty": 42,
"htmlString": "<html></html>",
"dictionary": {
"WHIZbang": "1",
"FOO": "2",
"Bar": "3"
}
}
Json.NET 9.0.1 introduced the NamingStrategy class hierarchy to handle this sort of issue. It extracts the logic for algorithmic remapping of property names from the contract resolver to a separate, lightweight class that allows for control of whether dictionary keys, explicitly specified property names, and extension data names (in 10.0.1) are remapped.
By using DefaultContractResolver and setting NamingStrategy to an instance of CamelCaseNamingStrategy you can generate JSON with camel-cased property names and unmodified dictionary keys by setting it in JsonSerializerSettings.ContractResolver:
var resolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = false,
OverrideSpecifiedNames = true
}
};
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = resolver;
Notes:
The current implementation of CamelCasePropertyNamesContractResolver also specifies that .Net members with explicitly specified property names (e.g. ones where JsonPropertyAttribute.PropertyName has been set) should have their names remapped:
public CamelCasePropertyNamesContractResolver()
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true,
OverrideSpecifiedNames = true
};
}
The above resolver preserves this behavior. If you don't want this, set OverrideSpecifiedNames = false.
Json.NET has several built-in naming strategies including:
CamelCaseNamingStrategy. A camel case naming strategy that contains the name-remapping logic formerly embedded in CamelCasePropertyNamesContractResolver.
SnakeCaseNamingStrategy. A snake case naming strategy.
DefaultNamingStrategy. The default naming strategy. Property names and dictionary keys are unchanged.
Or, you can create your own by inheriting from the abstract base class NamingStrategy.
While it is also possible to modify the NamingStrategy of an instance of CamelCasePropertyNamesContractResolver, since the latter shares contract information globally across all instances of each type, this can lead to unexpected side-effects if your application tries to use multiple instances of CamelCasePropertyNamesContractResolver. No such problem exists with DefaultContractResolver, so it is safer to use when any customization of casing logic is required.
That is a very nice answer. But why not just override the ResolveDictionaryKey?
class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
{
#region Overrides of DefaultContractResolver
protected override string ResolveDictionaryKey(string dictionaryKey)
{
return dictionaryKey;
}
#endregion
}
The selected answer is perfect but I guess by the time I'm typing this, the contract resolver must change to something like this because DictionaryKeyResolver doesn't exists anymore :)
public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
{
JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
contract.PropertyNameResolver = propertyName => propertyName;
return contract;
}
}