Is there a way to ignore get-only properties in Json.NET without using JsonIgnore attributes?
Asked Answered
F

4

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?

Frontogenesis answered 31/8, 2013 at 1:9 Comment(0)
M
93

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"}
Metope answered 31/8, 2013 at 13:54 Comment(6)
how about ignore only the properties without SET? And still serialize when having private set like public string Name { get; private set; } ?Cuttle
This solution is problematic because of get-only properties introduced in C#6.Eunuchize
@KasparKallas How so?Metope
@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.Eunuchize
I believe it works the same as System.Text.Json's IgnoreReadOnlyProperties on the JsonSerializerOptionsAbdel
Ignore property defined in list: public class ProductContractResolver: DefaultContractResolver { HashSet<string> excludeProperties = new HashSet<string>() { "OnStock", "B2FPrice" }; protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { IList<JsonProperty> props = base.CreateProperties(type, memberSerialization); return props .Where(p => !excludeProperties.Contains(p.PropertyName)) .ToList(); } }Shayshaya
I
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);
Incalescent answered 31/8, 2013 at 2:22 Comment(0)
I
14

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.

Inaccessible answered 6/12, 2015 at 0:32 Comment(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.Eunuchize
F
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.

Florous answered 22/12, 2016 at 7:53 Comment(1)
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.Pic

© 2022 - 2024 — McMap. All rights reserved.