How to ignore JsonProperty(PropertyName = "someName") when serializing json?
Asked Answered
S

3

30

I have some C# code using ASP.Net MVC, which is making use of Json.Net to serialize some DTOs. In order to reduce payload, I have made use of the [JsonProperty(PropertyName = "shortName")] attribute to use shorter property names during serialization.

This works great when the client is another .Net app or service, as the deserialization puts the object hierarchy back together, using the longer more friendly names, while keeping actual transfer payload low.

The problem comes into play when the client is javascript/ajax through a browser. It makes the request, and gets the json ... but that json is using the shortened less-friendly names.

How can I make the json.net serialization engine ignore the [JsonProperty(PropertyName = "shortName")] attribute programmatically? Ideally, my MVC service is going to sit there running and normally serialize using the shortened property names. When my code detects a particular parameter, I'd like to get the data serialized using the longer names and ignore the [JsonProperty()] attribute.

Any suggestions?

Thanks,

Kevin

Schuh answered 16/12, 2013 at 22:44 Comment(5)
just remove the JsonProperty and return an anonymous object according to the parameter you passed. something like new{UserName=uname}Penstock
I think the best approach would be to make a custom serializer (with json.NET, not from scratch) and remove the annotations. Make short names vs long names a setting of the serializer and just tell it which you want at the time of serialization. json.NET has no support for using/ignoring annotations at run time. If they are there at compile time, they will be used (baring some major hacking).Virtue
I appreciate the comments there. @evanmcdonnal: if we went with a custom serializer, would we need to do this on a DTO-specific level (attached to every DTO?) We have many DTOs spread accross many solutions through our many development teams. Ideally we could find a relatively elegant way to attach into the serialization process without having to touch and maintain this through all DTOs accross the board.Schuh
This can be done using a custom contract resolver. See my answer below.Afterburning
@Schuh that really depends, you could do it in a general way without making any changes to the DTO's. In my code base there is some abstraction on top of json.NET. If I were to do the same I would be making my changes at that level and I would do something in the serializer like get the property name using reflection then take a substring of that to get the short name. Of course there's the minor trade off of losing control over the short names, but it would be a simple general solution.Virtue
A
65

This can be done pretty easily using a custom contract resolver. Here's all the code you would need:

class LongNameContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        // Let the base class create all the JsonProperties 
        // using the short names
        IList<JsonProperty> list = base.CreateProperties(type, memberSerialization);

        // Now inspect each property and replace the 
        // short name with the real property name
        foreach (JsonProperty prop in list)
        {
            prop.PropertyName = prop.UnderlyingName;
        }

        return list;
    }
}

Here's a quick demo using the resolver:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo
        {
            CustomerName = "Bubba Gump Shrimp Company",
            CustomerNumber = "BG60938"
        };

        Console.WriteLine("--- Using JsonProperty names ---");
        Console.WriteLine(Serialize(foo, false));
        Console.WriteLine();
        Console.WriteLine("--- Ignoring JsonProperty names ---");
        Console.WriteLine(Serialize(foo, true));
    }

    static string Serialize(object obj, bool useLongNames)
    {
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Formatting = Formatting.Indented;
        if (useLongNames)
        {
            settings.ContractResolver = new LongNameContractResolver();
        }

        return JsonConvert.SerializeObject(obj, settings);
    }
}

class Foo
{
    [JsonProperty("cust-num")]
    public string CustomerNumber { get; set; }
    [JsonProperty("cust-name")]
    public string CustomerName { get; set; }
}

Output:

--- Using JsonProperty names ---
{
  "cust-num": "BG60938",
  "cust-name": "Bubba Gump Shrimp Company"
}

--- Ignoring JsonProperty names ---
{
  "CustomerNumber": "BG60938",
  "CustomerName": "Bubba Gump Shrimp Company"
}
Afterburning answered 17/12, 2013 at 16:37 Comment(3)
Love you Brian for this perfect answer. Hug from a fellow developer :)Instil
Tbh I wish there was an easier solutionAbagail
Also, I think IgnoreJsonPropertyNameContractResolver would be a better name for this resolver.Beak
M
2

Just want to "extend" Brian's answer with Deserializer class,

static T Deserialize<T>(string json)
{
    return JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings()
    {
        ContractResolver = new LongNameContractResolver()
    });
}
Mcvey answered 19/2, 2020 at 21:18 Comment(0)
C
2

In case you are using .Net 7 and System.Text.Json and trying to achieve the same result here's a solution by utilizing DefaultJsonTypeInfoResolver (found it from another post and simplified it a bit):

1- Define a custom attribute rather than using JsonPropertyName to be used for deserialization:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true)]
public sealed class JsonAlternativeNameAttribute : Attribute
{
    public JsonAlternativeNameAttribute(string? name) => this.Name = name;
    public string? Name { get; private set; }
}

2- Add an extension class with following implementation:

using System.Reflection;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Linq;

namespace MyApp.Core.Extension;

    public static class JsonSerializationExtensions
    {
        private static Action<JsonTypeInfo> AlternativeNamesContract() =>
            static typeInfo =>
            {
                if (typeInfo.Kind != JsonTypeInfoKind.Object)
                    return;
                foreach (var property in typeInfo.Properties)
                {
                    if (property.AttributeProvider?.GetCustomAttributes(typeof(JsonAlternativeNameAttribute), true) is { } list && list.Length > 0)
                        property.Name = list.OfType<JsonAlternativeNameAttribute>().FirstOrDefault()?.Name ?? property.GetMemberName() ?? property.Name;
                }
            };
    
        private static string? GetMemberName(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.Name;
    
        public static JsonSerializerOptions DefaultDeserializerOptions = 
            new () 
            {
                TypeInfoResolver = new DefaultJsonTypeInfoResolver
                {
                    Modifiers = { AlternativeNamesContract() }
                }
            };

        public static JsonSerializerOptions DefaultSerializerOptions =
            new()
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                PropertyNameCaseInsensitive = true,
                WriteIndented = true
            };
    }

3- Use the DefaultDeserializerOptions from the extension class to deserialize your model based on decorated name from JsonAlternativeName attribute

class Foo
{
    [JsonAlternativeName("cust-num")]
    public string CustomerNumber { get; set; }
    [JsonAlternativeName("cust-name")]
    public string CustomerName { get; set; }
}

var foo = JsonSerializer.Deserialize<Foo>("your foo json here", JsonSerializationExtensions.DefaultDeserializerOptions);

4- To serialize objects base on the property names you can use your custom JsonSerializerOptions without setting TypeInfoResolver. DefaultSerializerOptions in the extension class is a sample that can be used here:

var jsonString = JsonSerializer.Serialize(new Foo {CustomerName="Joe", CustomerNumber="123"}, JsonSerializationExtensions.DefaultSerializerOptions);

and the serialization result would be:

{
  "customerNumber": "123",
  "customerName": "Joe"
}
Corlisscorly answered 25/8, 2023 at 2:11 Comment(1)
I'm glad you posted this! I read about this when they blogged about it in .NET 7, but I didn't know if I'd need it. Like many, I want to have 1 class model for deserializing from external API's with different property names, but then serialize it using actual property names. I'm glad .NET caches the modifier, otherwise I wouldn't use it. This is a big help to me now.Napolitano

© 2022 - 2024 — McMap. All rights reserved.