25

While I've found plenty of approaches to deserializing specific properties while preventing them from serializing, I'm looking for the opposite behavior.

I've found plenty of questions asking the inverse:

Making a property deserialize but not serialize with json.net

Can I instruct Json.NET to deserialize, but not serialize, specific properties?

JSON.Net - Use JsonIgnoreAttribute only on serialization (But not when deserialzing)

How can I serialize a specific property, but prevent it from deserializing back to the POCO? Is there an attribute I can use to decorate the specific property?

Basically I'm looking for an equivalent to the ShouldSerialize* methods for deserialization.

I know I can write a custom converter, but that seems like overkill for this.

Edit:

Here's a little more context. The reason behind this is my class looks like:

public class Address : IAddress
{
    /// <summary>
    /// Gets or sets the two character country code
    /// </summary>
    [JsonProperty("countryCode")]
    [Required]
    public string CountryCode { get; set; }

    /// <summary>
    /// Gets or sets the country code, and province or state code delimited by a vertical pipe: <c>US|MI</c>
    /// </summary>
    [JsonProperty("countryProvinceState")]
    public string CountryProvinceState
    {
        get
        {
            return string.Format("{0}|{1}", this.CountryCode, this.ProvinceState);
        }
        set
        {
            if (!string.IsNullOrWhiteSpace(value) && value.Contains("|"))
            {
                string[] valueParts = value.Split('|');
                if (valueParts.Length == 2)
                {
                    this.CountryCode = valueParts[0];
                    this.ProvinceState = valueParts[1];
                }
            }
        }
    }

    [JsonProperty("provinceState")]
    [Required]
    public string ProvinceState { get; set; }
}

I need the CountryProvinceState property for the request, but I don't want it to deserialize back and trigger the setter logic.

Community
  • 1
  • 1
RJ Cuthbertson
  • 1,458
  • 1
  • 20
  • 36
  • How you want to do a non-standard thing without customization? – Matías Fidemraizer Jul 30 '15 at 18:26
  • I want to change the behavior without having to write and register a custom converter class. Similarly, there are multiple options for handling the reverse of this situation (using attributes or adding ShouldSerialize* methods) without having to write a custom converter class. – RJ Cuthbertson Jul 30 '15 at 18:29
  • 2
    Apparently I'm not the only person trying to do this either, as I just found this question from earlier today: http://stackoverflow.com/questions/31722033/json-net-webapi2-serialize-property-but-skip-during-deserialization – RJ Cuthbertson Jul 30 '15 at 18:38
  • What's an overkill is trying to fix something that can be already fixed ;P Design a simple attribute and mark those properties you want to not deserialize even when they're serialized and go! AFAIK Json.NET supports adding converters globally to all serialization calls. – Matías Fidemraizer Jul 30 '15 at 18:52

2 Answers2

24

Simplest method would be to mark the real property as [JsonIgnore] and create a get-only proxy property:

    /// <summary>
    /// Gets or sets the country code, and province or state code delimited by a vertical pipe: <c>US|MI</c>
    /// </summary>
    [JsonIgnore]
    public string CountryProvinceState
    {
        get
        {
            return string.Format("{0}|{1}", this.CountryCode, this.ProvinceState);
        }
        set
        {
            if (!string.IsNullOrWhiteSpace(value) && value.Contains("|"))
            {
                string[] valueParts = value.Split('|');
                if (valueParts.Length == 2)
                {
                    this.CountryCode = valueParts[0];
                    this.ProvinceState = valueParts[1];
                }
            }
        }
    }

    [JsonProperty("countryProvinceState")]
    string ReadCountryProvinceState
    {
        get { return CountryProvinceState; } 
    }

The proxy property can be private if you desire.

Update

If you have to do this for lots of properties in lots of classes, it might be easier to create your own ContractResolver that checks for a custom attribute. If found, the attribute would signal that the property is get-only:

[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class GetOnlyJsonPropertyAttribute : Attribute
{
}

public class GetOnlyContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (property != null && property.Writable)
        {
            var attributes = property.AttributeProvider.GetAttributes(typeof(GetOnlyJsonPropertyAttribute), true);
            if (attributes != null && attributes.Count > 0)
                property.Writable = false;
        }
        return property;
    }
}

Then use it like:

[JsonProperty("countryProvinceState")]
[GetOnlyJsonProperty]
public string CountryProvinceState { get; set; }

And then:

        var settings = new JsonSerializerSettings { ContractResolver = new GetOnlyContractResolver() };

        var address = JsonConvert.DeserializeObject<Address>(jsonString, settings);
dbc
  • 104,963
  • 20
  • 228
  • 340
  • Beautiful. Lemme test it and I'll accept once I validate that it works. Thank you. – RJ Cuthbertson Jul 30 '15 at 19:14
  • If you need to do this for *lots* of properties, it's quite easy to create a [`ContractResolver`](http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_DefaultContractResolver.htm) that checks for a custom attribute. Need it? – dbc Jul 30 '15 at 19:21
  • 3
    Worked like a charm when I tested it. I think this is the only one to be honest, but if you have it, I'm sure it would benefit people in the future. Posterity will thank you for your service to the world my friend. – RJ Cuthbertson Jul 30 '15 at 20:10
  • I think the ContractResolver is a lot cleaner way to do this, but the quick and dirty way works too! Thanks – RJ Cuthbertson Jul 30 '15 at 20:12
2

In your question you have a simple string property. But it's a bit more complicated when you have an object. The solution with .Writeable = false will not work, as deserialization will go to properties of an object. Consider the following code:

public class Constants
{
    public Address Headquarters { get; set; }

    public static Constants Instance = new Constants
    {
        Headquarters = new Address { Street = "Baker Street" }
    };
}
public class Address
{
    public string Street { get; set; }
}

public class Data
{
    [GetOnlyJsonProperty]
    // we want this to be included in the response, but not deserialized back
    public Address HqAddress { get { return Constants.Instance.Headquarters; } }
}

// somewhere in your code:
var data = JsonConvert.DeserializeObject<Data>("{'HqAddress':{'Street':'Liverpool Street'}}", settings);

Now JSON will still not try to create a new Addreess object for HqAddress property, as it only has getter. But then (even though .Writeable == false) it goes deeper and deserializes Street property, setting "Liverpool Street" into Constants.Instance.Heqdquarters object, overwriting data in Constants of your application.

Solution is: In a new version of Newtonsoft.JSON (I tried in v10), there is a new property ShouldDeserialize. So the resolver should be:

public class GetOnlyContractResolver : DefaultContractResolver
    {
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);
            if (property != null) // Change here (1)
            {
                var attributes = property.AttributeProvider.GetAttributes(typeof(GetOnlyJsonPropertyAttribute), true);
                if (attributes != null && attributes.Count > 0)
                    property.ShouldDeserialize = (a) => false;  // Change here (2)
            }
            return property;
        }
    }

(1) I removed the condition for && property.Writeable, so it processes the HqAddress and skips deserialization for a full tree. (2) ShouldDeserialize is a predicate, called on every object to deserialize. So you can conditionally skip only some properties. But here I made it simple for example.