59

Is there a way to ignore get-only properties using the Json.NET serializer but without using JsonIgnore attributes?

For example, I have a class with these get properties:

    public Keys Hotkey { get; set; }

    public Keys KeyCode
    {
        get
        {
            return Hotkey & Keys.KeyCode;
        }
    }

    public Keys ModifiersKeys
    {
        get
        {
            return Hotkey & Keys.Modifiers;
        }
    }

    public bool Control
    {
        get
        {
            return (Hotkey & Keys.Control) == Keys.Control;
        }
    }

    public bool Shift
    {
        get
        {
            return (Hotkey & Keys.Shift) == Keys.Shift;
        }
    }

    public bool Alt
    {
        get
        {
            return (Hotkey & Keys.Alt) == Keys.Alt;
        }
    }

    public Modifiers ModifiersEnum
    {
        get
        {
            Modifiers modifiers = Modifiers.None;

            if (Alt) modifiers |= Modifiers.Alt;
            if (Control) modifiers |= Modifiers.Control;
            if (Shift) modifiers |= Modifiers.Shift;

            return modifiers;
        }
    }

    public bool IsOnlyModifiers
    {
        get
        {
            return KeyCode == Keys.ControlKey || KeyCode == Keys.ShiftKey || KeyCode == Keys.Menu;
        }
    }

    public bool IsValidKey
    {
        get
        {
            return KeyCode != Keys.None && !IsOnlyModifiers;
        }
    }

Do I need to add [JsonIgnore] to all of them (I also have many other classes), or there is better way to ignore all get-only properties?

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Jaex
  • 4,204
  • 2
  • 34
  • 56

4 Answers4

91

You can do this by implementing a custom IContractResolver and using that during serialization. If you subclass the DefaultContractResolver, this becomes very easy to do:

class WritablePropertiesOnlyResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
        return props.Where(p => p.Writable).ToList();
    }
}

Here is a test program demonstrating how to use it:

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

class Program
{
    static void Main(string[] args)
    {
        Widget w = new Widget { Id = 2, Name = "Joe Schmoe" };

        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            ContractResolver = new WritablePropertiesOnlyResolver()
        };

        string json = JsonConvert.SerializeObject(w, settings);

        Console.WriteLine(json);
    }
}

class Widget
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string LowerCaseName
    {
        get { return (Name != null ? Name.ToLower() : null); }
    }
}

Here is the output of the above. Notice that the read-only property LowerCaseName is not included in the output.

{"Id":2,"Name":"Joe Schmoe"}
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • 6
    how about ignore only the properties without SET? And still serialize when having private set like public string Name { get; private set; } ? – Jaime Sangcap Aug 28 '16 at 13:22
  • This solution is problematic because of get-only properties introduced in C#6. – Kaspar Kallas Jan 22 '21 at 16:14
  • @KasparKallas [How so?](https://dotnetfiddle.net/GTBJzi) – Brian Rogers Jan 22 '21 at 16:32
  • @BrianRogers You could lose important information by not serializing get-only properties. But... that's what the OP asked... and your answer provides that. Sorry, I was caught up in my own use case instead. I wanted to not serialize *computed* properties. I believe that would be the more common use case nowadays but I could be wrong. – Kaspar Kallas Jan 24 '21 at 15:56
  • I believe it works the same as System.Text.Json's `IgnoreReadOnlyProperties` on the `JsonSerializerOptions` – 321X Sep 30 '21 at 13:26
18

Use the OptIn mode of JSON.net and you'll only need to decorate the properties you want to serialize. This isn't as good as automatically opting out all read only properties, but it can save you some work.

[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
    [JsonProperty]
    public string serializedProp { get; set; }

    public string nonSerializedProp { get; set; }
}

Udate: Added another possibility using reflection

If the above solution still isn't quite what you're looking for, you could use reflection to make dictionary objects which would then be serialized. Of course the example below will only work for simple classes, so you would need to add recursion if your classes contain other classes. This should at least point you in the right direction.

The subroutine to put the filtered result into a dictionary:

    private Dictionary<String, object> ConvertToDictionary(object classToSerialize)
    {
        Dictionary<String, object> resultDictionary = new Dictionary<string, object>();

        foreach (var propertyInfo in classToSerialize.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (propertyInfo.CanWrite) resultDictionary.Add(propertyInfo.Name, propertyInfo.GetValue(classToSerialize, null));
        }

        return resultDictionary;
    }

A snippet showing its use:

SampleClass sampleClass = new SampleClass();
sampleClass.Hotkey = Keys.A;
var toSerialize = ConvertToDictionary(sampleClass);
String resultText = JsonConvert.SerializeObject(toSerialize);
Lathejockey81
  • 1,198
  • 7
  • 8
12

You can use a contract resolver like this:

public class ExcludeCalculatedResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        property.ShouldSerialize = _ => ShouldSerialize(member);
        return property;
    }

    internal static bool ShouldSerialize(MemberInfo memberInfo)
    {
        var propertyInfo = memberInfo as PropertyInfo;
        if (propertyInfo == null)
        {
            return false;
        }

        if (propertyInfo.SetMethod != null)
        {
            return true;
        }

        var getMethod = propertyInfo.GetMethod;
        return Attribute.GetCustomAttribute(getMethod, typeof(CompilerGeneratedAttribute)) != null;
    }
}

It will exclude calculated properties but include C#6 get only properties and all properties with a set method.

Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
  • 1
    Thank you! This is a more correct solution than the accepted answer. It is missing a check whether the property has `JsonPropertyAttribute` defined which should override the `ContractResolver`. It is easy to do by using `JsonProperty.HasMemberAttribute`. – Kaspar Kallas Jan 22 '21 at 16:19
10

Json.net does have the ability to conditionally serialize properties without an attribute or contract resolver. This is especially useful if you don't want your project to have a dependency on Json.net.

As per the Json.net documentation

To conditionally serialize a property, add a method that returns boolean with the same name as the property and then prefix the method name with ShouldSerialize. The result of the method determines whether the property is serialized. If the method returns true then the property will be serialized, if it returns false then the property will be skipped.

Community
  • 1
  • 1
Mark
  • 21,067
  • 14
  • 53
  • 71
  • thanks for this, while not ideal it works. I don't want my abstractions to require the newtonsoft package, and I think they should have gone with a more generic solution such as the `System.ComponentModel.BrowsableAttribute` or ignore properties without setters by default. – Michael Brown Feb 22 '21 at 02:14