Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
470 views
in Technique[技术] by (71.8m points)

c# - Populate object where objects are reused and arrays are replaced?

Using Json.NET, I'd like to map a JObject onto a .NET object with the following behavior:

  1. For (non-null) array properties of the JObject, replace the entire collection on the target object.

  2. For (non-null) object properties of the JObject, reuse the target object's property if it's not null, and map just the provided properties onto it.

JsonSerializer.Populate seems to be what I want, as described in this answer. As for the behaviors I'm looking for, it seems I can achieve one or the other, but not both, via JsonSerializerSettings.ObjectCreationHandling. ObjectCreationHandling.Replace does what I want with respect to requirement #1, while ObjectCreationHandling.Auto does what I want with respect to requirement #2, but it appends array items onto the existing collection.

What is the recommended way to achieve both requirements here?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Json.NET will automatically replace any arrays or read-only collections. To clear out read-write collections when deserializing, you could create a custom contract resolver that adds an OnDeserializingCallback to every modifiable collection that clears the collection when deserialization begins. Clearing the collection rather that replacing it outright handles cases where the collection is get-only, for instance:

public class RootObject
{
    readonly HashSet<int> hashSet = new HashSet<int>();
    public HashSet<int> HashSetValues { get { return this.hashSet; } }
}

The contract resolver is as follows:

public class CollectionClearingContractResolver : DefaultContractResolver
{
    static void ClearGenericCollectionCallback<T>(object o, StreamingContext c)
    {
        var collection = o as ICollection<T>;
        if (collection == null || collection is Array || collection.IsReadOnly)
            return;
        collection.Clear();
    }

    static SerializationCallback ClearListCallback = (o, c) =>
        {
            var collection = o as IList;
            if (collection == null || collection is Array || collection.IsReadOnly)
                return;
            collection.Clear();
        };

    protected override JsonArrayContract CreateArrayContract(Type objectType)
    {
        var contract = base.CreateArrayContract(objectType);
        if (!objectType.IsArray)
        {
            if (typeof(IList).IsAssignableFrom(objectType))
            {
                contract.OnDeserializingCallbacks.Add(ClearListCallback);
            }
            else if (objectType.GetCollectItemTypes().Count() == 1)
            {
                MethodInfo method = typeof(CollectionClearingContractResolver).GetMethod("ClearGenericCollectionCallback", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
                MethodInfo generic = method.MakeGenericMethod(contract.CollectionItemType);
                contract.OnDeserializingCallbacks.Add((SerializationCallback)Delegate.CreateDelegate(typeof(SerializationCallback), generic));
            }
        }

        return contract;
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static IEnumerable<Type> GetCollectItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }
}

public static class JsonExtensions
{
    public static void Populate<T>(this JToken value, T target) where T : class
    {
        value.Populate(target, null);
    }

    public static void Populate<T>(this JToken value, T target, JsonSerializerSettings settings) where T : class
    {
        using (var sr = value.CreateReader())
        {
            JsonSerializer.CreateDefault(settings).Populate(sr, target);
        }
    }
}

Then to use it, do:

var settings = new JsonSerializerSettings
{
    ContractResolver = new CollectionClearingContractResolver(),
};
jObject.Populate(rootObject, settings);

Sample fiddle.

Such a contract resolver would also be useful when deserializing objects that populate collections in their default constructor, as in Deserialization causes copies of List-Entries.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...