0

I have implemented a JsonSerializer with a "custom" ContractResolver which overrides the binding-flags so I can actually serialize private and protected members. As I want to serialize objects in specific classes. Making everything just "public" is not an option.

So I implemented code which works almost well, for attributes on same class. However when there are protected attrs from the parent-class are involved, it generates some strange extra information in JSON-string called "k__Backingfield".

First of all, the result/problem:

json-debug: 
{
"jsonTestPrivate":"child-class",
"jsonTestProtected":"Attr from parent class",
"<jsonTestPrivate>k__BackingField":"child-class" //<<--- I want to get rid of THIS :(
}

This problem ONLY seems to occur for derived attributed that are protected in parent-class.

--

The code:

namespace XYZ
{
    class CustomJsonSerializerSettings : Newtonsoft.Json.Serialization.DefaultContractResolver
    {
        public CustomJsonSerializerSettings ContractResolver = null;

        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                            .Select(p => base.CreateProperty(p, memberSerialization))
                        .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                                   .Select(f => base.CreateProperty(f, memberSerialization)))
                        .ToList();
            

            props.ForEach(p => { p.Writable = true; p.Readable = true; });

            return props;
        }
    }
}

My parent-class which abstracts the SerializeJson function that must be introduced in every Serializable Object and supplied parent attributes which make the problem:

namespace XYZ
{
    public abstract class CommandResponse
    {
        public abstract string SerializeJson();
        protected string jsonTestProtected { get; set; } = "Attr from parent class";
        
        //Helper class to serialize/unserialize objects on higher scope.
        //Just derive from this class for every JSON-response/able object and implement abstract function
    }
}

And my child-class which implements the actual function, including some comments on things that I have already tested.

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Converters;

namespace XYZ
{
    class CommandActionInvalid : CommandResponse
    {
        private string jsonTestPrivate { get; set; } = "child-class";

        public override string SerializeJson()
        {
            DefaultContractResolver resolverObj = new CustomJsonSerializerSettings();

            //This attributes dont seem to do anything, even if referenced alot in stackoverflow :(
            /*
            resolverObj.IgnoreSerializableAttribute = true;
            resolverObj.IgnoreSerializableInterface = true;
            resolverObj.IgnoreShouldSerializeMembers = true;
            resolverObj.SerializeCompilerGeneratedMembers = false;
            */

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

            return JsonConvert.SerializeObject(this, settings);
        }
    }
}
Steini
  • 2,753
  • 15
  • 24
  • 1
    The `k__Backingfield` fields are the secret, compiler-generated backing fields for auto-implemented properties. See [Is it possible to access backing fields behind auto-implemented properties?](https://stackoverflow.com/q/8817070/3744182). If you want to skip them and other compiler-generated fields see [Determine if FieldInfo is compiler generated backingfield](https://stackoverflow.com/q/2638913/3744182). Do those two questions answer yours? – dbc Mar 21 '21 at 15:34
  • This helped me atleast to solve it "the dirty way". I checked props for name containing BackingField and could exclude them bty setting the prop to ignored = true; However I could sadly not cast ".IsDefined(typeof(CompilerGeneratedAttribute), false);" (The function IsDefined) does not exist on JsonProperty. What class does it belong to, so I can access this? I rather like to have a clean implementation instead of a hard-coded string im checking for. But aside that, atleast I can continue working for now. :) – Steini Mar 21 '21 at 15:49
  • [`MemberInfo.IsDefined(Type, Boolean)`](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.memberinfo.isdefined?view=net-5.0) is a method of `MemberInfo`. You would use it to filter the `FieldInfo` objects not the `JsonProperty` objects, i.e. `type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(fi => !fi.IsDefined(typeof(CompilerGeneratedAttribute), false))`. – dbc Mar 21 '21 at 15:52
  • To answer, why I dont like the "dirty way" - There are programmers that are happy that it "just works" and there are programmers who like to have a little bit of code quality and the solution should also never break even incase of updates in the library or similar. My dirty way was just a proof of concept yet so I can manipulate it and researching in the right place. However I like to have a more decent way to determine then checking for a hardcoded-string. – Steini Mar 21 '21 at 15:56
  • 1
    You might also want to use [`BindingFlags.FlattenHierarchy`](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=net-5.0) which *Specifies that public and protected static members up the hierarchy should be returned.* Not certain because I don't really understand what you are trying to do here. – dbc Mar 21 '21 at 15:57
  • *never break even incase of updates in the library* -- well if you are serializing **and deserializing** private fields, then deserialization is likely to break when the library updates. That's why they are private. If you are just serializing for archival or debugging purposes then you are more likely to be OK. – dbc Mar 21 '21 at 15:59
  • Okay made it, to actually check for propery "CompilerGeneratedAttribute". However printing it resolved in all attrs (my attribute and also the "BakingField" all return "false". So I could not distinguish here // [jsonTestPrivate] -> isdefined:False [jsonTestProtected] -> isdefined:False [k__BackingField] -> isdefined:False – Steini Mar 21 '21 at 15:59
  • Then can you please [edit] your question to share a [mcve] showing the current state of your code and specifically where the problem is? – dbc Mar 21 '21 at 16:00
  • 1
    For your use case, in order to have private or proteced fields serialized, it would be enough to give them the `[JsonProperty]` attribute. – Heinz Kessler Mar 21 '21 at 16:12
  • @HeinzKessler Thanks, that actually solved my issue on a way better-way. I was not aware that those properties exist. It seems I made a big work-arround for something already built-in. Well okay I learned alot of new things! – Steini Mar 21 '21 at 16:18
  • Your question still lacks a [mcve] but is this what you want? https://dotnetfiddle.net/uJSwMd – dbc Mar 21 '21 at 16:35
  • By _Attribute_ do you mean _Property_? An _Attribute_ is a thing in C#; i.e., it means something very specific (and, it doesn't mean Property) – Flydog57 Mar 21 '21 at 16:41

1 Answers1

0

The k__Backingfield fields are the secret, compiler-generated backing fields for auto-implemented properties. If you want to skip them while serializing all other fields and properties, your custom contract resolver can be rewritten as follows:

class CustomJsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
    const BindingFlags allInstanceMembersFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy;

    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        IEnumerable<MemberInfo> fields = 
            objectType.GetFields(allInstanceMembersFlags)
                // Skip k__BackingField secret backing fields of auto-implemented properties.
                .Where(fi => !fi.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)));
        IEnumerable<MemberInfo> props = objectType.GetProperties(allInstanceMembersFlags)
                // Skip indexed properties like List[0].
                .Where(pi => pi.GetIndexParameters().Length == 0);
        return fields.Concat(props).ToList();
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        switch (member)
        {
            case FieldInfo fi:
                // Passing MemberSerialization.Fields causes the JsonProperty to be marked as readable and writable
                return base.CreateProperty(fi, MemberSerialization.Fields);
            case PropertyInfo pi:
                var jsonProperty = base.CreateProperty(pi, memberSerialization);
                // Mark the property as readable and writable if the corresponding get and set methods exist even if not public
                if (pi.GetGetMethod(true) != null)
                    jsonProperty.Readable = true;
                if (pi.GetSetMethod(true) != null)
                    jsonProperty.Writable = true;
                return jsonProperty;
            default:
                return base.CreateProperty(member, memberSerialization);
        }
    }
}

Notes:

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340