JSON.Net: Force serialization of all private fields and all fields in sub-classes
Asked Answered
W

5

54

I have a class with several different classes and I send the information in these classes out to clients but I don't want to send them all out so some are private, some have the [JsonObject(MemberSerialization.OptIn)] flag etc.

However, now I want to do a backup of all these objects when I need to shutdown the server and every 12 hours (I don't want to use a database) so what I want to do (if possible) is to force the JSON.Net Serializer to convert the object and all the object belonging to that object.

For example:

class Foo
{
  public int Number;
  private string name;
  private PrivateObject po = new PrivateObject();

  public string ToJSON()
  { /* Serialize my public field, my property and the object PrivateObject */ }
}

I tried this code (even though it's obsolete) but it doesn't Serialize the objects related to my object:

Newtonsoft.Json.JsonSerializerSettings jss = new Newtonsoft.Json.JsonSerializerSettings();

Newtonsoft.Json.Serialization.DefaultContractResolver dcr = new Newtonsoft.Json.Serialization.DefaultContractResolver();
dcr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic;
jss.ContractResolver = dcr;

return Newtonsoft.Json.JsonConvert.SerializeObject(this, jss);
Walkin answered 8/6, 2014 at 14:11 Comment(0)
S
67

This should work:

var settings = new JsonSerializerSettings() { ContractResolver = new MyContractResolver() };
var json = JsonConvert.SerializeObject(obj, settings);

public class MyContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
    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;
    }
}
Shum answered 8/6, 2014 at 14:23 Comment(12)
Is it possible to include properties that doesn't have [JsonProperty] and it's class [JsonObject(MemberSerialization.OptIn)]?Walkin
@user2524586 of course, JsonProperty and JsonObject are not must. They are just hints for JsonConvertShum
Badly written of me. I mean, in the ContractResolver can I also include properties that doesn't have the JsonProperty attribute set eventhough the [JsonObject(MemberSerialization.OptIn)] is set on the class so that I will get all the fields and properties (eventhough 5 out of 10 have the JsonProperty attribute set on them)?Walkin
@user2524586 In ContractResolver you decide which field/property to use in serialization/deserialization process. So whether a property has some specific attribute, whether it is public or not, it is all up to you. See for ex, this question: #22042806Shum
You might want to check the type parameter (depending on the complexity of the object you're serializing). I had exceptions when JSON.NET tried to find certain fields on the wrong type.Wonderment
c.f. answer below - modified for .NET 2.0, your approach seems to work fine. Thanks!Eldreeda
This works great, but if you're just trying to "store an object as-is" so you can load it later without any hassle, you may run into problems with readonly properties. Properties usually don't need to be stored anyway for these types of situations, so just gather the fields and you should be fine. The solution given picks up the fields that back auto generated properties anyway, so there's no need to save the properties for this use case.Clobber
And how to deserialize it after it?Lorenelorens
@Lorenelorens Have you tried anything before commenting(asking)? Obviously Not. Use the same settings while deserialization.Shum
This was helpful. -- I made a minor modification, in that I added a .Where(prop => prop.GetCustomAttributes(typeof(JsonProperty)).Any()) so that I would only pickup non-public properties that opted in to being serialized.Greenlaw
this method is too slowCavatina
@MustafaSalihASLIM it will definitely as it is using Reflection. Try caching some parts of it using a Dictionary maybe?Ansate
E
5

@L.B's answer is great. But ... it requires .NET 3.5 or above.

For those of us stuck with 2.0 ...

public class ForceJSONSerializePrivatesResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
    protected override IList<Newtonsoft.Json.Serialization.JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
    {
        var props = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

        List<Newtonsoft.Json.Serialization.JsonProperty> jsonProps = new List<Newtonsoft.Json.Serialization.JsonProperty>();

        foreach( var prop in props )
        {
        jsonProps.Add( base.CreateProperty(prop, memberSerialization));
        }

        foreach( var field in type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) )
        {
        jsonProps.Add ( base.CreateProperty( field, memberSerialization ) );
        }

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

...seems to work.

Eldreeda answered 7/6, 2015 at 19:35 Comment(3)
I don't remember. Does .Net 2.0 support lamba expr ( jsonProps.ForEach(p => { p.Writable = true; p.Readable = true; }); ), Is ForEach available in 2.0?Shum
It seems to. msdn.microsoft.com/en-us/library/bwabdf9z(v=vs.80).aspx + ditto with lambda in the MSDN docs -- but I'm using/testing 2.0 inside Unity3d, which sometimes grabs bits from 3.5, so I might have got it wrong here.Eldreeda
@Shum When a new answer is posted, the question gets the front page in the Questions section in StackOverflow, just as a new question would...Eal
P
2

Awesome thanks @L.B. Here's a full implementation in a .linq script in case anyone wants to test with private subclasses - e.g. See A has private subclass B.

void Main()
{
    var a = A.Test();
    SerialiseAllFields.Dump(a);
}

class A
{
    private int PrivField1;
    private int PrivProp1 { get; set; }
    private B PrivSubClassField1;

    public static A Test()
    {
        return new A { PrivField1 = 1, PrivProp1 = 2, PrivSubClassField1 = B.Test() };
    }
}

class B
{
    private int PrivField1;
    private int PrivProp1 { get; set; }

    public static B Test()
    {
        return new B { PrivField1 = 3, PrivProp1 = 4 };
    }
}

// Define other methods and classes here
public static class SerialiseAllFields
{
    public static void Dump(object o, bool indented = true)
    {
        var settings = new Newtonsoft.Json.JsonSerializerSettings() { ContractResolver = new AllFieldsContractResolver() };
        if (indented)
        {
            settings.Formatting = Newtonsoft.Json.Formatting.Indented;
        }
        Newtonsoft.Json.JsonConvert.SerializeObject(o, settings).Dump();
    }
}

public class AllFieldsContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
    protected override IList<Newtonsoft.Json.Serialization.JsonProperty> CreateProperties(Type type, Newtonsoft.Json.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;
    }
}

The interesting thing is that the backing fields for the properties are also serialized i.e. output is:

{
  "PrivProp1": 2,
  "PrivField1": 1,
  "<PrivProp1>k__BackingField": 2,
  "PrivSubClassField1": {
    "PrivProp1": 4,
    "PrivField1": 3,
    "<PrivProp1>k__BackingField": 4
  }
}
Pouch answered 28/6, 2016 at 9:49 Comment(2)
You can add .Where(p=>!p.PropertyName.Contains("k__BackingField")) just before the ToList() statement to eliminate the backing fields.Fig
There is a builtin silution for it: "if ( field.IsDefined( typeof( CompilerGeneratedAttribute ), false ) == true )"Robrobaina
C
1

this is modified version of the previous accepted answer, this will also serialize private fields/properties. performance a bit increased. (serialization without BinaryFormatter a bit slower)

public class CloneContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type,
                                MemberSerialization memberSerialization)
    {
        List<MemberInfo> members = GetSerializableMembers(type);
        if (members == null)
         throw new JsonSerializationException("Null collection of serializable members returned.");

        members.AddRange(type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
            .Where(f => !f.CustomAttributes.Any(x => x.AttributeType
                == typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute))));

        members.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
            .Where(f => !f.CustomAttributes.Any(x => x.AttributeType
                == typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute))));

        JsonPropertyCollection properties = new JsonPropertyCollection(type);
        members.ForEach(member =>
        {
            JsonProperty property = CreateProperty(member, memberSerialization);
            property.Writable = true;
            property.Readable = true;
            properties.AddProperty(property);
        });
        return properties;
    }
}

in my opinion, this method will use a bit more memory

public static class CloneHelper
{
    private readonly static JsonSerializerSettings clone_settings = new JsonSerializerSettings() { ContractResolver = new CloneContractResolver() };
    public static T Clone<T>(this object source)
    {
        T entity = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, clone_settings), clone_settings);
        return entity;
    }
}
Cavatina answered 6/5, 2021 at 12:13 Comment(0)
K
0

After inspect into newtonSoft.Json. Discover newton ignore private members because the property DefaultMemberSearchFlags of DefaultContractResolver is set to BindingFlags.Instance | BindingFlags.Public. So we just need to change this field to BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic.

Create a JsonSerializerSettings

private static readonly JsonSerializerSettings JsonSetting = new()
{
    ContractResolver = new IgnorePropertiesResolver()
    {
        DefaultMembersSearchFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
    }
};

And use this setting in Serialization or Deserialization.

JsonConvert.SerializeObject(slzObj, Formatting.Indented, JsonSetting);

Works in newtonsoft.Json v3.2.1 in Unity.

(But IDE rider warning DefaultMembersSearchFlags is obsolete. Don't know if this works in other version of newtonsoft)

Korfonta answered 14/2 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.