Making a property deserialize but not serialize with json.net
Asked Answered
A

14

185

We have some configuration files which were generated by serializing C# objects with Json.net.

We'd like to migrate one property of the serialised class away from being a simple enum property into a class property.

One easy way to do this, would be to leave the old enum property on the class, and arrange for Json.net to read this property when we load the config, but not to save it again when we next serialize the object. We'll deal with generating the new class from the old enum separately.

Is there any simple way to mark (e.g. with attributes) a property of a C# object, so that Json.net will ignore it ONLY when serializing, but attend to it when deserializing?

Archibold answered 19/7, 2012 at 15:25 Comment(6)
What about a custom converter : you can use it as an attribute on your property, override ReadJson and WriteJson with different comportments, no ? Example (not exactly what you need, but...) weblogs.asp.net/thangchung/archive/2010/08/26/…Unseam
OnDeserialized attribute can be a work-around for youPodgorica
Shouldn't that be possible using the `[JsonIgnore]' attribute?? james.newtonking.com/archive/2009/10/23/…Open
Are you able to expand on how this can be used in one direction only, as per the last paragraph of the q?Archibold
It is possible to use [JsonIgnore] in combination with a secondary private setter that is decorated with a [JsonProperty] attribute. There are a couple of other simple solutions as well. I've added a detailed writeup.Surovy
After 1 year, my answer below only has one vote, but if you remove the get; from the property, the JSON serialiser can't read it, so it will no longer export the old value.Octastyle
S
178

There are actually several fairly simple approaches you can use to achieve the result you want.

Let's assume, for example, that you have your classes currently defined like this:

class Config
{
    public Fizz ObsoleteSetting { get; set; }
    public Bang ReplacementSetting { get; set; }
}

enum Fizz { Alpha, Beta, Gamma }

class Bang
{
    public string Value { get; set; }
}

And you want to do this:

string json = @"{ ""ObsoleteSetting"" : ""Gamma"" }";

// deserialize
Config config = JsonConvert.DeserializeObject<Config>(json);

// migrate
config.ReplacementSetting = 
    new Bang { Value = config.ObsoleteSetting.ToString() };

// serialize
json = JsonConvert.SerializeObject(config);
Console.WriteLine(json);

To get this:

{"ReplacementSetting":{"Value":"Gamma"}}

Approach 1: Add a ShouldSerialize method

Json.NET has the ability to conditionally serialize properties by looking for corresponding ShouldSerialize methods in the class.

To use this feature, add a boolean ShouldSerializeBlah() method to your class where Blah is replaced with the name of the property that you do not want to serialize. Make the implementation of this method always return false.

class Config
{
    public Fizz ObsoleteSetting { get; set; }

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Note: if you like this approach but you don't want to muddy up the public interface of your class by introducing a ShouldSerialize method, you can use an IContractResolver to do the same thing programmatically. See Conditional Property Serialization in the documentation.

Approach 2: Manipulate the JSON with JObjects

Instead of using JsonConvert.SerializeObject to do the serialization, load the config object into a JObject, then simply remove the unwanted property from the JSON before writing it out. It's just a couple of extra lines of code.

JObject jo = JObject.FromObject(config);

// remove the "ObsoleteSetting" JProperty from its parent
jo["ObsoleteSetting"].Parent.Remove();

json = jo.ToString();

Approach 3: Clever (ab)use of attributes

  1. Apply a [JsonIgnore] attribute to the property that you do not want to be serialized.
  2. Add an alternate, private property setter to the class with the same type as the original property. Make the implementation of that property set the original property.
  3. Apply a [JsonProperty] attribute to the alternate setter, giving it the same JSON name as the original property.

Here is the revised Config class:

class Config
{
    [JsonIgnore]
    public Fizz ObsoleteSetting { get; set; }

    [JsonProperty("ObsoleteSetting")]
    private Fizz ObsoleteSettingAlternateSetter
    {
        // get is intentionally omitted here
        set { ObsoleteSetting = value; }
    }

    public Bang ReplacementSetting { get; set; }
}
Surovy answered 14/6, 2014 at 21:28 Comment(4)
We solved it in our project (which uses an internal integration-specific super set of the base model, where none of the superclass properties should be serialized), by setting the get-properties to internal. Having public setters allowed Web Api to set the properties, but stopped it from serializing them.Eleph
In conjunction with using the JsonPropertyAttribute, from C# 6.0 you can use the nameof keyword instead of using "magic strings". This makes refactoring a lot easier and fool-proof - plus, if you do miss renaming any occurrences, the compiler will warn you, anyway. Using @Brian's example, usage would be this : [JsonProperty(nameof(ObsoleteSetting))]Semicentennial
It's a bad idea to use nameof() in JsonProperty declarations, particularly in this legacy scenario. The JSON represents an external (and hopefully eternal) contract with another interface, and you definitely do NOT want to change the name of the JSON property if you refactor. You would be breaking compatibility will all existing JSON files and components that generate JSON in this format. In fact, you'd be better off putting JsonProperty(…) with the full name on every serialized property to make sure they don't change if you later rename the property.Fantoccini
[JsonProperty(nameof(ObsoleteSetting))] in this case is basically a no-op. The main reason to use JsonProperty is to make sure the JSON property name is fixed, whereas the C# property name can be changed. This allows you to refactor without breaking your API contract.Lashanda
R
74

For any situation where it's acceptable to have your deserialization-only property be marked internal, there's a remarkably simple solution that doesn't depend on attributes at all. Simply mark the property as internal get, but public set:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

This results in correct deserialization using default settings/resolvers/etc., but the property is stripped from serialized output.

Rickart answered 27/2, 2018 at 18:29 Comment(7)
Simple yet clever solution.Artificer
Due note that the property will be ignored by the validation module too. (So you cannot mark it as [Required] for deserialization anymore, as this relies on a public get method).Lexi
This does not work with either internal nor private. It is always serialized.Ironing
This did not work for me. Got a property not found error when deserializing.Mensch
I can't help but wonder if the last two commenters put the internal access modifier on the setter rather than the getter. This works exactly as advertised for me when using Json.NET. Maybe System.Text.Json works differently though.Offish
worked perfectly and by far the simplest suggestionAriew
Although this solution is "simple" in terms of code size, it does not express intent, so there is a risk of someone misunderstanding the solution to accidentally break it during "code cleanup". Repurposing is usually bad.Lashanda
C
51

I like sticking with attributes on this one, here is the method I use when needing to deserialize a property but not serialize it or vice versa.

STEP 1 - Create the custom attribute

public class JsonIgnoreSerializationAttribute : Attribute { }

STEP 2 - Create a custom Contract Reslover

class JsonPropertiesResolver : DefaultContractResolver
{
    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        //Return properties that do NOT have the JsonIgnoreSerializationAttribute
        return objectType.GetProperties()
                         .Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute)))
                         .ToList<MemberInfo>();
    }
}

STEP 3 - Add attribute where serialization is not needed but deserialization is

    [JsonIgnoreSerialization]
    public string Prop1 { get; set; } //Will be skipped when serialized

    [JsonIgnoreSerialization]
    public string Prop2 { get; set; } //Also will be skipped when serialized

    public string Prop3 { get; set; } //Will not be skipped when serialized

STEP 4 - Use it

var sweet = JsonConvert.SerializeObject(myObj, new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() });

Hope this helps! Also it's worth noting that this will also ignore the properties when Deserialization happens, when I am derserializing I just use the converter in the conventional way.

JsonConvert.DeserializeObject<MyType>(myString);
Condescending answered 5/2, 2016 at 22:50 Comment(6)
Thanks for this helpful implementation. Is there a way to extend the base implementation of GetSerializableMembers rather than override it entirely?Koziel
Nevermind, just realized it was as simple as: return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();Koziel
not sure why this isn't the top-rated answer. it's clean, follows newtonsoft's patterns and is easy to do. only thing i would add is that you can set it up globally using JsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }Mede
nevermind, this doesn't actually do what the asker is asking for. this is basically recreating the JsonIgnore. it does not skip the property for serialization but set the property during deserialization because there is no way for the GetSerializableMembers method to know if it's a read or write so you're excluding the properties for both. this solution does not work.Mede
this is EXACTLY the solution I had in my head when I searched.Lobito
@MattM - the key here is to NOT add this custom serializer when deserializing :)Clincher
Y
11

Use setter property:

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { _ignoreOnSerializing = value; } }

[JsonIgnore]
private string _ignoreOnSerializing;

[JsonIgnore]
public string IgnoreOnSerializing
{
    get { return this._ignoreOnSerializing; }
    set { this._ignoreOnSerializing = value; }
}

Hope this help.

Yoicks answered 13/6, 2014 at 10:10 Comment(2)
Thanks. Note that the JsonProperty should have an uppercase IgnoreOnSerializing, equal to the property. I recommend using nameof(IgnoreOnSerializing) to avoid magic string, in case of renaming.Buettner
Same as https://mcmap.net/q/135543/-making-a-property-deserialize-but-not-serialize-with-json-net, just more complicated. Works, but is a code smell: learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/…Lashanda
D
5

After i spent a quite long time searching how to flag a class property to be De-Serializable and NOT Serializable i found that there's no such thing to do that at all; so i came up with a solution that combines two different libraries or serialization techniques (System.Runtime.Serialization.Json & Newtonsoft.Json) and it worked for me like the following:

  • flag all your class and sub-classes as "DataContract".
  • flag all the properties of your class and sub-classes as "DataMember".
  • flag all the properties of your class and sub-classes as "JsonProperty" except those you want them not to be serialized.
  • now flag the properties the you do NOT want it to be serialized as "JsonIgnore".
  • then Serialize using "Newtonsoft.Json.JsonConvert.SerializeObject" and De-Serialize using "System.Runtime.Serialization.Json.DataContractJsonSerializer".

    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Runtime.Serialization.Json;
    using System.Text;
    
    namespace LUM_Win.model
    {
        [DataContract]
        public class User
        {
            public User() { }
            public User(String JSONObject)
            {
                MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(JSONObject));
                DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(User));
    
                User user = (User)dataContractJsonSerializer.ReadObject(stream);
                this.ID = user.ID;
                this.Country = user.Country;
                this.FirstName = user.FirstName;
                this.LastName = user.LastName;
                this.Nickname = user.Nickname;
                this.PhoneNumber = user.PhoneNumber;
                this.DisplayPicture = user.DisplayPicture;
                this.IsRegistred = user.IsRegistred;
                this.IsConfirmed = user.IsConfirmed;
                this.VerificationCode = user.VerificationCode;
                this.Meetings = user.Meetings;
            }
    
            [DataMember(Name = "_id")]
            [JsonProperty(PropertyName = "_id")]
            public String ID { get; set; }
    
            [DataMember(Name = "country")]
            [JsonProperty(PropertyName = "country")]
            public String Country { get; set; }
    
            [DataMember(Name = "firstname")]
            [JsonProperty(PropertyName = "firstname")]
            public String FirstName { get; set; }
    
            [DataMember(Name = "lastname")]
            [JsonProperty(PropertyName = "lastname")]
            public String LastName { get; set; }
    
            [DataMember(Name = "nickname")]
            [JsonProperty(PropertyName = "nickname")]
            public String Nickname { get; set; }
    
            [DataMember(Name = "number")]
            [JsonProperty(PropertyName = "number")]
            public String PhoneNumber { get; set; }
    
            [DataMember(Name = "thumbnail")]
            [JsonProperty(PropertyName = "thumbnail")]
            public String DisplayPicture { get; set; }
    
            [DataMember(Name = "registered")]
            [JsonProperty(PropertyName = "registered")]
            public bool IsRegistred { get; set; }
    
            [DataMember(Name = "confirmed")]
            [JsonProperty(PropertyName = "confirmed")]
            public bool IsConfirmed { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "verification_code")]
            public String VerificationCode { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "meeting_ids")]
            public List<Meeting> Meetings { get; set; }
    
            public String toJSONString()
            {
                return JsonConvert.SerializeObject(this, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            }
        }
    }
    

Hope that helps ...

Domel answered 2/4, 2013 at 18:58 Comment(2)
Bravo Ahmed Abulazm. thanks it did save me from lot of work. :)Deeannadeeanne
Using two serialization frameworks is not something I will actively push against.Lobito
A
3

Depending on where in the application this takes place and if it's just one property, one manual way you can do this is by setting the property value to null and then on the model you can specify that the property be ignored if the value is null:

[JsonProperty(NullValueHandling = NullValue.Ignore)]
public string MyProperty { get; set; }

If you are working on an ASP.NET Core web app, you can globally set this for all properties in all models by setting this in your Startup.cs file:

public void ConfigureServices(IServiceCollection services) {
    // other configuration here
    services.AddMvc()
        .AddJsonOptions(options => options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore);
}
Allheal answered 31/5, 2019 at 17:6 Comment(0)
O
2

with reference to @ThoHo's solution, using the setter is actually all that is needed, with no additional tags.

For me I previously had a single reference Id, that I wanted to load and add to the new collection of reference Ids. By changing the definition of the reference Id to only contain a setter method, which added the value to the new collection. Json can't write the value back if the Property doesn't have a get; method.

// Old property that I want to read from Json, but never write again. No getter.
public Guid RefId { set { RefIds.Add(value); } }

// New property that will be in use from now on. Both setter and getter.
public ICollection<Guid> RefIds { get; set; }

This class is now backwards compatible with the previous version and only saves the RefIds for the new versions.

Octastyle answered 21/11, 2016 at 3:39 Comment(0)
C
2

To build upon Tho Ho's answer, this can also be used for fields.

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { IgnoreOnSerializing = value; } }

[JsonIgnore]
public string IgnoreOnSerializing;
Chios answered 26/10, 2017 at 9:20 Comment(1)
Works, but is a code smell ("write-only property"): learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/…Lashanda
P
1

thats will do the trick, create a property with set only

example 1: https://dotnetfiddle.net/IxMXcG

[JsonProperty("disabled-protections")]
public JArray DisabledProtections { set => IsPartialResult = (value != null && value.HasValues); }

public bool IsPartialResult { get; private set; }

example 2:

private JArray _disabledProtections;

[JsonProperty("disabled-protections")]
public JArray DisabledProtections { set => _disabledProtections = value; }

public bool IsPartialResult => _disabledProtections != null && _disabledProtections.HasValues;
Penetrant answered 6/4, 2022 at 7:16 Comment(1)
This works, but is a code smell ("write-only propery"): learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/…Lashanda
K
0

If you use JsonConvert,IgnoreDataMemberAttribute is ok.My standard library not refrence Newton.Json,and I use [IgnoreDataMember] to control object serialize.

From Newton.net help document.

Koziarz answered 26/12, 2018 at 5:14 Comment(0)
F
0

Is there any simple way to mark (e.g. with attributes) a property of a C# object, so that Json.net will ignore it ONLY when serializing, but attend to it when deserializing?

The easiest way I've found as of this writing is to include this logic in your IContractResolver.

Sample code from above link copied here for posterity:

public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }

    public bool ShouldSerializeManager()
    {
        // don't serialize the Manager property if an employee is their own manager
        return (Manager != this);
    }
}

public class ShouldSerializeContractResolver : DefaultContractResolver
{
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
        {
            property.ShouldSerialize =
                instance =>
                {
                    Employee e = (Employee)instance;
                    return e.Manager != e;
                };
        }

        return property;
    }
}

All of the answers are good but this approach seemed like the cleanest way. I actually implemented this by looking for an attribute on the property for SkipSerialize and SkipDeserialize so you can just mark up any class you control. Great question!

Fierce answered 29/6, 2019 at 4:37 Comment(0)
T
0

Jraco11's answer is very neat. In case, if you want to use the same IContractResolver both for serialization and deserialization, then you can use the following:

public class JsonPropertiesResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (member.IsDefined(typeof(JsonIgnoreSerializationAttribute)))
        {
            property.ShouldSerialize = instance => false;
        }

        return property;
    }
}
Taratarabar answered 3/2, 2021 at 8:23 Comment(0)
P
0

Inspired by Daniel Saidi's comment, with examples and tests added.

By example, let's say we have a Car class and we changed the name of the Marque property to Make, and we want it backwards compatible with the json we got when serializing it back when it was called Marque.

My new Car class looks like this:

public class Car
{
    public string Make { get; set; }

    /// <summary>Only for deserialization of legacy json.</summary>
    [Obsolete($"Use '{nameof(Make)}' instead.")]
    public string Marque 
    {
        set => this.Make = value; 
    }

    public string Model { get; set; }
    public int EngineCapacity { get; set; }

    public Car(string make, string model, int engineCapacity)
    {
        Make = make;
        Model = model;
        EngineCapacity = engineCapacity;
    }
}

Note that the obsolete Marque property as only a setter - no getter. We've marked it Obsolete - it is there for only for deserialization of legacy data, no one should be using it, and we'll have no complaints about write-only properties.

Here are my tests for confirming that the legacy property is not outputted when serializing, and is honoured when deserializing. Also confirms that it works for System.Text.Json and Newtonsoft.Json.

[TestMethod]
public void TestLegacyJson()
{
    var vitesse = new Car("Triumph", "Vitesse", 1998);
    checkCar(vitesse);

    string json = System.Text.Json.JsonSerializer.Serialize(vitesse);
    // Private getter means Marque is not serialized.
    Assert.AreEqual("{\"Make\":\"Triumph\",\"Model\":\"Vitesse\",\"EngineCapacity\":1998}", json);

    string newtonsoftJson = Newtonsoft.Json.JsonConvert.SerializeObject(vitesse);
    Assert.AreEqual("{\"Make\":\"Triumph\",\"Model\":\"Vitesse\",\"EngineCapacity\":1998}", newtonsoftJson);

    checkCar(System.Text.Json.JsonSerializer.Deserialize<Car>(json));
    checkCar(Newtonsoft.Json.JsonConvert.DeserializeObject<Car>(json));

    string oldJson = "{\"Marque\":\"Triumph\",\"Model\":\"Vitesse\",\"EngineCapacity\":1998}";

    checkCar(System.Text.Json.JsonSerializer.Deserialize<Car>(oldJson));
    checkCar(Newtonsoft.Json.JsonConvert.DeserializeObject<Car>(oldJson));
}

private void checkCar(Car? car)
{
    if (car is null)
        throw new ArgumentNullException(nameof(car), "Deserialization failure.");

    Assert.AreEqual("Triumph", car.Make);
    Assert.AreEqual("Vitesse", car.Model);
    Assert.AreEqual(1998, car.EngineCapacity);
}
Procurator answered 18/4 at 10:59 Comment(0)
F
-4

Use [JsonIgnore] attribute in the public property of the model class.

Fevre answered 23/6, 2021 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.