1

I would like to serialize only certain properties of an object using Json.NET.
I'm using a solution like the one described in the post Json.net serialize only certain properties.
My problem is that I would like to select different properties each time, and calls to CreateContract (which in turn calls CreateProperties) are being cached for performance reasons (source code: https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs).

Is there a way to serialize only the properties I want, specifying different properties each time, possibly without re-writing the whole DefaultContractResolver class?

Here is a program that shows this problem:

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

    class Person {
        public int Id;
        public string FirstName;
        public string LastName;
    }

    public class SelectedPropertiesContractResolver<T> : CamelCasePropertyNamesContractResolver {

        HashSet<string> _selectedProperties;

        public SelectedPropertiesContractResolver(IEnumerable<string> selectedProperties) {
            _selectedProperties = selectedProperties.ToHashSet();
        }

        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) {
            if (type == typeof(T)) {
                return base.CreateProperties(type, memberSerialization)
                    .Where(p => _selectedProperties.Contains(p.PropertyName, StringComparer.OrdinalIgnoreCase)).ToList();
            }
            return base.CreateProperties(type, memberSerialization);
        }

    }


    class Program {

        static void Main(string[] args) {

            var person = new Person { Id = 1, FirstName = "John", LastName = "Doe" };

            var serializer1 = new JsonSerializer {
                ContractResolver = new SelectedPropertiesContractResolver<Person>(new[] { "Id", "FirstName" })
            };

            // This will contain only Id and FirstName, as expected
            string json1 = JObject.FromObject(person, serializer1).ToString();

            var serializer2 = new JsonSerializer {
                ContractResolver = new SelectedPropertiesContractResolver<Person>(new[] { "LastName" })
            };

            // Since calls to CreateProperties are cached, this will contain Id and FirstName as well, instead of LastName.
            string json2 = JObject.FromObject(person, serializer2).ToString();

        }
    }
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • 2
    Possible duplicate of https://stackoverflow.com/questions/25157511/newtonsoft-add-jsonignore-at-runtime ? – Murray Foxcroft Feb 15 '19 at 14:11
  • 2
    as @MurrayFoxcroft says: the first thing to try here is the `ShouldSerialize*` ("conditional serialization") pattern, which [Json.NET supports](https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm) – Marc Gravell Feb 15 '19 at 14:13
  • Thanks, conditional serialization seems the way to go. – Paolo Tedesco Feb 15 '19 at 14:16

3 Answers3

3

You can override the ResolveContract method and always create new contract (or even better - provide your own fancy way of caching according to type T and _selectedProperties content)

public class SelectedPropertiesContractResolver<T> : CamelCasePropertyNamesContractResolver {

    ...

    public override JsonContract ResolveContract(Type type)
    {
        return CreateContract(type);
    }
}
  • This is nice, but it relies on the fact that the base implementation of `ResolveContract` does nothing else thatn calling CreateContract and caching the result... – Paolo Tedesco Feb 15 '19 at 14:49
  • Well, any override of `object.GetHashCode` relies on fact that its base implementation does nothing other than getting hash code. I see same here, `ResolveContract` method resolves the contract for a given type and if base implementation does anything other than that then I'd say that base implementation has a problem. – Sergey Shevchenko Feb 15 '19 at 15:09
  • However you can add the call 'base.ResolveContract' and ignore the result. But it will be meaningless unless base implementations does something specific. – Sergey Shevchenko Feb 15 '19 at 15:12
1

You can use the code for resolve your task:

        static void Main(string[] args)
        {
            var myObject = new {Id = 123, Name = "Test", IsTest = true};
            var propertyForSerialization = new List<string> { "Id", "Name" };

            var result = GetSerializedObject(myObject, propertyForSerialization);
        }

        private static string GetSerializedObject(object objForSerialize, List<string> propertyForSerialization)
        {
            var customObject = new ExpandoObject() as IDictionary<string, Object>;
            Type myType = objForSerialize.GetType();

            IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());

            foreach (PropertyInfo prop in props)
            {
                foreach (var propForSer in propertyForSerialization)
                {
                    if (prop.Name == propForSer)
                    {
                        customObject.Add(prop.Name, prop.GetValue(objForSerialize, null));
                    }
                }
            }

           return JsonConvert.SerializeObject(customObject);
        }
PWND
  • 409
  • 3
  • 11
1

A couple of possible solutions, based on comments and the selected answer.

Using conditional serialization:

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

    public interface ISerializeSelectedPropertiesOnly {
        bool ShouldSerialize(string propertyName);
    }

    public class Person : ISerializeSelectedPropertiesOnly {
        public int Id;
        public string FirstName;
        public string LastName;
        public HashSet<string> _propertiesToSerialize;
        public bool ShouldSerialize(string propertyName) {
            return _propertiesToSerialize?.Contains(propertyName, StringComparer.OrdinalIgnoreCase) ?? true;
        }
    }

    public class SelectedPropertiesContractResolver : CamelCasePropertyNamesContractResolver {
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
            JsonProperty property = base.CreateProperty(member, memberSerialization);
            if (typeof(ISerializeSelectedPropertiesOnly).IsAssignableFrom(property.DeclaringType)) {
                property.ShouldSerialize = instance => ((ISerializeSelectedPropertiesOnly)instance).ShouldSerialize(property.PropertyName);
            }
            return property;
        }
    }

    class Program {
        static void Main(string[] args) {
            var person = new Person { Id = 1, FirstName = "John", LastName = "Doe" };
            person._propertiesToSerialize = new HashSet<string> { "Id", "FirstName" };
            var serializer = new JsonSerializer {
                ContractResolver = new SelectedPropertiesContractResolver()
            };
            string json1 = JObject.FromObject(person, serializer).ToString();
            person._propertiesToSerialize = new HashSet<string> { "LastName" };
            string json2 = JObject.FromObject(person, serializer).ToString();
        }
    }

Overriding ResolveContract:

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

    public class Person {
        public int Id;
        public string FirstName;
        public string LastName;
    }

    public class SelectedPropertiesContractResolver<T> : CamelCasePropertyNamesContractResolver {
        HashSet<string> _selectedProperties;
        public SelectedPropertiesContractResolver(IEnumerable<string> selectedProperties) {
            _selectedProperties = selectedProperties.ToHashSet();
        }
        public override JsonContract ResolveContract(Type type) {
            return CreateContract(type);
        }
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) {
            if (type == typeof(T)) {
                return base.CreateProperties(type, memberSerialization)
                    .Where(p => _selectedProperties.Contains(p.PropertyName, StringComparer.OrdinalIgnoreCase)).ToList();
            }
            return base.CreateProperties(type, memberSerialization);
        }
    }


    class Program {
        static void Main(string[] args) {
            var person = new Person { Id = 1, FirstName = "John", LastName = "Doe" };
            var serializer = new JsonSerializer {
                ContractResolver = new SelectedPropertiesContractResolver<Person>(new HashSet<string> { "Id", "FirstName" })
            };
            string json1 = JObject.FromObject(person, serializer).ToString();
            serializer = new JsonSerializer {
                ContractResolver = new SelectedPropertiesContractResolver<Person>(new HashSet<string> { "LastName" })
            };
            string json2 = JObject.FromObject(person, serializer).ToString();
            Console.WriteLine(json1);
            Console.WriteLine(json2);
        }
    } 
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193