If you want to use the same classes to automatically generate JSON with different property names, without having to write a custom JsonConverter
for each one, you're going to need to create your own custom ContractResolver
, for instance:
If you have a deterministic way to map all .Net property names to the appropriate property names for a given usage context (post or get-from-api), you can create a contract resolver like one of the above.
If, however, there's no general rule for mapping .Net property names for JSON property names, and each context may require some per-property customization, you could create your own ContractResolver
that applies in a specific named context, and your own System.Attribute
that supplies a JSON context name and property name to use in this context. I.e.:
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
public class JsonConditionalNameAttribute : System.Attribute
{
readonly string contextName;
readonly string propertyName;
public string ContextName { get { return contextName; } }
public string PropertyName { get { return propertyName; } }
public JsonConditionalNameAttribute(string contextName, string propertyName)
{
this.contextName = contextName;
this.propertyName = propertyName;
}
}
public class ConditionalNameContractResolver : DefaultContractResolver
{
readonly string contextName;
public string ContextName { get { return contextName; } }
public ConditionalNameContractResolver(string contextName)
: base()
{
if (string.IsNullOrEmpty(contextName))
throw new ArgumentNullException();
if (string.IsNullOrEmpty(contextName))
throw new ArgumentException();
this.contextName = contextName;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
var attrs = jProperty.AttributeProvider.GetAttributes(typeof(JsonConditionalNameAttribute), true)
.Cast<JsonConditionalNameAttribute>()
.Where(a => a.ContextName == ContextName)
.Select(a => a.PropertyName)
.Distinct()
.ToList();
if (attrs.Count == 1)
{
jProperty.PropertyName = attrs[0];
}
else if (attrs.Count > 1)
{
throw new JsonSerializationException(string.Format("Multiple conditional property attributes found for "{0}" in in context "{1}": "{2}"", jProperty, contextName, String.Join(",", attrs)));
}
return jProperty;
}
}
Then your binding model would look something like:
public static class AddressBindingModelContexts
{
public const string Post = "post";
public const string GetFromApi = "getFromApi";
}
public class AddressBindingModel
{
[JsonConditionalName(AddressBindingModelContexts.GetFromApi, "address_1")]
[JsonConditionalName(AddressBindingModelContexts.Post, "address1")]
public string Address1 { get; set; }
[JsonConditionalName(AddressBindingModelContexts.GetFromApi, "address_2")]
[JsonConditionalName(AddressBindingModelContexts.Post, "address2")]
public string Address2 { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("county")]
public string County { get; set; }
[JsonProperty("postcode")]
public string PostCode { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonConditionalName(AddressBindingModelContexts.GetFromApi, "save_as")]
[JsonConditionalName(AddressBindingModelContexts.Post, "saveAs")]
public string SaveAs { get; set; }
}
To test:
var jsonFromApi = GetJsonFromApi();
var postContract = new ConditionalNameContractResolver(AddressBindingModelContexts.Post);
var getFromApiContract = new ConditionalNameContractResolver(AddressBindingModelContexts.GetFromApi);
var model = JsonConvert.DeserializeObject<AddressBindingModel>(jsonFromApi, new JsonSerializerSettings { ContractResolver = getFromApiContract });
var postJson = JsonConvert.SerializeObject(model, Formatting.Indented, new JsonSerializerSettings { ContractResolver = postContract });
Debug.WriteLine(postJson); // Verify the postJson has the necessary properties and data.
To change the contract resolver for all results returned from Web API, see JSON and XML Serialization in ASP.NET Web API: Camel Casing. To use a custom contract resolver when returning results from a specific Web Api call, see Customize Json result in Web API.