57

I have a .Net Core Web API. It automatically maps models when the model properties match the request body. For example, if you have this class:

public class Package
{
    public string Carrier { get; set; }
    public string TrackingNumber { get; set; }
}

It would correctly bind it to a POST endpoint if the request body is the following JSON:

{
    carrier: "fedex",
    trackingNumber: "123123123"
}

What I need to do is specify a custom property to map. For example, using the same class above, I need to be able to map to JSON if the TrackingNumber comes in as tracking_number.

How do I do that?

9 Answers 9

122

TejSoft's answer does not work in ASP.NET Core 3.0 Web APIs by default.

Starting in 3.0, the ASP.NET Core Json.NET (Newtonsoft.Json) sub-component is removed from the ASP.NET Core shared framework. It is announced that, "Json.NET will continue to work with ASP.NET Core, but it will not be in the box with the shared framework." The newly added Json Api claimed to be specifically geared for high-performance scenarios.

Use JsonPropertyName attribute to set a custom property name:

using System.Text.Json.Serialization;

public class Package
{
    [JsonPropertyName("carrier")]
    public string Carrier { get; set; }

    [JsonPropertyName("tracking_number")]
    public string TrackingNumber { get; set; }
}
Sign up to request clarification or add additional context in comments.

5 Comments

This is the correct answer as of .net core 3.0. JsonProperty was NOT working for us and it was difficult to figure out why. Making this change cleared everything up.
and I lost hours to figure out that... o.o
Doesn't work in .NET 7. It does result in serialization working properly, but model binding doesn't. Instead, I've had to add a BindProperty attribute to every property I want to be bound using snake_case property names.
@Cocowalla I have tested in .NET7 and .NET8 preview like this: JsonSerializer.Deserialize<Package>(JsonSerializer.Serialize(package)); and it seems working.
@MertCanIlis my mistake, I have some endpoints that take multipart form encoding instead of JSON, and that's where it wasn't working for me
51

Change your package class and add JsonProperty decoration for each field you wish to map to a different json field.

public class Package
{
    [JsonProperty(PropertyName = "carrier")]
    public string Carrier { get; set; }

    [JsonProperty(PropertyName = "trackingNumber")]
    public string TrackingNumber { get; set; }
}

2 Comments

[JsonProperty(PropertyName = "tracking_Number")]
What happens if I want to use the XML serialization ?
23

I think that this should work too:

using Microsoft.AspNetCore.Mvc;
public class Package
{
     [BindProperty(Name ="carrier")]
     public string Carrier { get; set; }

     [BindProperty(Name ="trackingNumber")]
     public string TrackingNumber { get; set; }
}

3 Comments

In ASP.NET Core, this does not work for models, but it does work when you want to bind "global" properties (present in multiple/all requests) to the controller itself. See Use BindProperty Attribute for Model Binding to Properties.
@rsenna I'm on .NET 7, and this does work for models - it's the only thing I've found so far that does work!
@Cocowalla good to know. My previous comment is from 4 years ago, so it’s not up to date anymore.
5

In case of request:

[BindProperty(Name = "tracking_number")]
public string TrackingNumber { get; set; }

In case of response:

[JsonPropertyName("tracking_number")]
public string TrackingNumber { get; set; }

In both cases:

[BindProperty(Name = "tracking_number")]
[JsonPropertyName("tracking_number")]
public string TrackingNumber { get; set; }

Comments

4

By using custom converter you will be able to achieve what you need.
The following suite of components based on attributes might suit your needs and is quite generic in case you want to expand it.

Base attribute class

Defines the IsMatch which allows you to define if the object property matches the json property.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public abstract class JsonDeserializationPropertyMatchAttribute : Attribute
{
    protected JsonDeserializationPropertyMatchAttribute() { }

    public abstract bool IsMatch(JProperty jsonProperty);
}

Sample implementation: multi deserialization names

Defines an attribute which allows you to have multiple names associated to a property. The IsMatch implementation simply loops through them and tries to find a match.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class JsonDeserializationNameAttribute : JsonDeserializationPropertyNameMatchAttribute
{
    public string[] PropertyNames { get; private set; }

    public JsonDeserializationNameAttribute(params string[] propertyNames)
    {
        this.PropertyNames = propertyNames;
    }

    public override bool IsMatch(JProperty jsonProperty)
    {
        return PropertyNames.Any(x => String.Equals(x, jsonProperty.Name, StringComparison.InvariantCultureIgnoreCase));
    }
}

The Converter in order to bind both attributes to the json deserialization the following converter is required:

public class JsonDeserializationPropertyMatchConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsClass;
    }

    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var constructor = objectType.GetConstructor(new Type[0]);
        if (constructor == null)
            throw new JsonSerializationException("A parameterless constructor is expected.");

        var value = constructor.Invoke(null);

        var jsonObject = JObject.Load(reader);
        var jsonObjectProperties = jsonObject.Properties();

        PropertyInfo[] typeProperties = objectType.GetProperties();
        var typePropertyTuples = new List<Tuple<PropertyInfo, Func<JProperty, bool>>>();
        foreach (var property in typeProperties.Where(x => x.CanWrite))
        {
            var attribute = property.GetCustomAttribute<JsonDeserializationPropertyMatchAttribute>(true);
            if (attribute != null)
                typePropertyTuples.Add(new Tuple<PropertyInfo, Func<JProperty, bool>>(property, attribute.IsMatch));
            else
                typePropertyTuples.Add(new Tuple<PropertyInfo, Func<JProperty, bool>>(property, (x) => false));
        }

        foreach (JProperty jsonProperty in jsonObject.Properties())
        {
            var propertyTuple = typePropertyTuples.FirstOrDefault(x => String.Equals(jsonProperty.Name, x.Item1.Name, StringComparison.InvariantCultureIgnoreCase) || x.Item2(jsonProperty));
            if (propertyTuple != null)
                propertyTuple.Item1.SetValue(value, jsonProperty.Value.ToObject(propertyTuple.Item1.PropertyType, serializer));
        }

        return value;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Sample using the codes pasted above, and by decorating the class as follow, I managed to get the objects to deserialize properly:

[JsonConverter(typeof(JsonDeserializationPropertyMatchConverter))]
public class Package
{
    public string Carrier { get; set; }

    [JsonDeserializationName("Tracking_Number","anotherName")]
    public string TrackingNumber { get; set; }
}

Output 1

var input = "{ carrier: \"fedex\", trackingNumber: \"123123123\" }";
var output = JsonConvert.DeserializeObject<Package>(input); // output.TrackingNumber is "123123123"

Output 2

var input = "{ carrier: \"fedex\", tracking_Number: \"123123123\" }";
var output = JsonConvert.DeserializeObject<Package>(input); // output.TrackingNumber is "123123123"

Output 3

var input = "{ carrier: \"fedex\", anotherName: \"123123123\" }";
var output = JsonConvert.DeserializeObject<Package>(input); // output.TrackingNumber is "123123123"

Comments

2

For some reason following did not work for me in my model class:

[JsonPropertyName("carrier")]
 public string Carrier { get; set; }

whereas this worked:

[JsonProperty(PropertyName = "carrier")]
public string Carrier { get; set; }

one being from system.text.Json library and other from Newtonsoft.json but I really want to know the reason why so. Since I was trying return the property value in Json string in API call.

2 Comments

I also faced this issue. Did you find the reason why so? In my case, "[JsonPropertyName("carrier")]" worked for a few properties, and for the remaining properties I have to use "[JsonProperty(PropertyName = "carrier")]" within a single model.
It depends on the JsonSerializer used. JsonPropertyName is used by System.Text.Json and JsonProperty is used by NewtonsoftJson
0

In My Case, I don't want to change property name Carrier & TrackingNumber

so I just add new JsonSerializerSettings() on JsonResult response

public JsonResult GetJQXGridData(){
    var Data = .......
    return Json(Data, new JsonSerializerSettings()) //change here
}

Without using JsonSerializerSettings Output

{
    carrier: "fedex",
    trackingNumber: "123123123"
}

With using JsonSerializerSettings Output

{
    Carrier: "fedex",
    TrackingNumber: "123123123"
}

Comments

0

For DotnetCore3.1 We can use

public class Package
{

    [JsonProperty("carrier")]
    public string Carrier { get; set; }

    [JsonProperty("trackingNumber")]
    public string TrackingNumber { get; set; }
}

Comments

0

If you use XML serialization (ContentTypes.TextXml) you may use attribute [XmlElement(ElementName = "name")] to change field name. With using namespace System.Xml.Serialization.

I tried [JsonProperty("name")] and [System.Runtime.Serialization.DataMember(Name = "name")] but they didn't work in my case for XML content.

For more info read https://learn.microsoft.com/en-us/dotnet/standard/serialization/controlling-xml-serialization-using-attributes

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.