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);
ns16
  • 1,322
  • 2
  • 17
  • 26
Westerlund.io
  • 2,743
  • 5
  • 30
  • 37

4 Answers4

66

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;
    }
}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • Is it possible to include properties that doesn't have [JsonProperty] and it's class [JsonObject(MemberSerialization.OptIn)]? – Westerlund.io Jun 09 '14 at 19:04
  • 1
    @user2524586 of course, `JsonProperty` and `JsonObject` are not *must*. They are just hints for JsonConvert – L.B Jun 09 '14 at 19:21
  • 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)? – Westerlund.io Jun 09 '14 at 19:25
  • @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: http://stackoverflow.com/questions/22042306/determining-properties-to-serialize-in-json-net-based-on-user – L.B Jun 09 '14 at 19:32
  • 2
    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. – Peter Sep 08 '14 at 09:00
  • c.f. answer below - modified for .NET 2.0, your approach seems to work fine. Thanks! – Adam Jun 07 '15 at 19:36
  • 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. – Carlos Sanchez Sep 08 '16 at 18:29
  • And how to deserialize it after it? – EgoPingvina Apr 13 '17 at 12:39
  • @EgoPingvina Have you tried anything before commenting(asking)? Obviously Not. Use the same *settings* while deserialization. – L.B Apr 13 '17 at 21:42
  • 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. – BrainSlugs83 Jun 14 '17 at 19:42
  • 1
    this method is too slow – Mustafa Salih ASLIM May 06 '21 at 08:11
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.

Adam
  • 32,900
  • 16
  • 126
  • 153
  • 2
    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? – L.B Jun 07 '15 at 21:21
  • It seems to. https://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. – Adam Jun 07 '15 at 22:46
  • @L.B When a new answer is posted, the question gets the front page in the Questions section in StackOverflow, just as a new question would... – yoel halb Nov 04 '15 at 16:20
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
  }
}
Ilan
  • 1,647
  • 1
  • 15
  • 16
  • 1
    You can add `.Where(p=>!p.PropertyName.Contains("k__BackingField"))` just before the `ToList()` statement to eliminate the backing fields. – user2441511 Jun 26 '17 at 20:38
  • 1
    There is a builtin silution for it: "if ( field.IsDefined( typeof( CompilerGeneratedAttribute ), false ) == true )" – György Gulyás Dec 03 '18 at 18:40
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;
    }
}