JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.74k stars 3.25k forks source link

Suggestion: Allow copying settings among JsonSerializerSettings instances #1319

Open weitzhandler opened 7 years ago

weitzhandler commented 7 years ago

Hi,

Please expose a constructor of JsonSerializerSettings that allows copying of all the settings from another one. See this question.

Example*:

class AppJsonSerializerSettings : JsonSerializerSettings {  }    
JsonSerializerSettings settings = JsonSerializerSettingsProvider.CreateSerializerSettings();
var appSettings = new AppJsonSerializerSettings(settings)
  { TypeNameHandling = TypeNameHandling.Auto }
JamesNK commented 7 years ago

Why are you inheriting from JsonSerializeSettings?

I don't see any need for this. Values from JsonSerializerSettings are already copied by JsonSerializer using JsonSerializer.Create

weitzhandler commented 7 years ago

@JamesNK I want to have a centralized way to provide all my settings.

JamesNK commented 7 years ago

I don't see why you need to to inherit from JsonSerializerSettings to do that.

weitzhandler commented 7 years ago

How do I provide a modified version of my main settings to the JsonConvert functions, adding various converters/binders/settings one-time only, but still persisting all the rest of my default settings? Besides, now that JsonSerializerSettings wasn't marked sealed, at least 321 people are doing this.

JamesNK commented 7 years ago

Wrap JsonConvert inside your own helper methods, do it there, and call your helper methods instead of JsonConvert. Inheriting from JsonSerializerSettings wouldn't help you.

weitzhandler commented 7 years ago

It does help, and it actually works.

I have my own subclass of the JsonSerializerSettings, which I set Json.NET's default settings with. The reason I have it this way is so I can override the settings and branch it to many presets, read on. Then, in specific places, I need a different set of settings (based on my default settings), to achieve that I call JsonConvert.(De)Serialize providing a subclass of my previous subclass adding some more changes in its constructor or after initialization. It's not clear at all that this is not the way to do it, in fact, I think it's much easier to recon JsonSerializerSettings should be provided dynamically by inheriting etc., than wrapping the static JsonConvert, but since this is your repo, and I clarified how I see those things (have I not?) and after consideration you still decided it's wrong, just go ahead and close the issue and I and the rest of those people who share a similar POV as me will keep copying the properties the way I've been doing it so far.

And we will all still appreciate you very much for this awesome product of yours, which is perfect anyhow!

daviburg commented 4 years ago

Seal or no seal a deep-copy constructor is useful for operations such as 'every setting like this instance except for 1 property'.

vhe1 commented 2 years ago

Nonwithstanding the inheritance issue, I have a related problem:

We use a set of default settings. So, we create it like this:

private static void SetSettings()
{
    var xxx = JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings();
    xxx.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
    JsonConvert.DefaultSettings = () => xxx;
}

Now, if some components build up the default settings during setup, it looks similar:

private static void SetAdditionalSettings()
{
    var xxx = JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings();
    xxx.Converters.Add(new XDeserializer());
    JsonConvert.DefaultSettings = () => xxx;
}

Now, the issue here is that the first method implicitly stores the xxx object in the executioncontext of the lambda. So, the second method doesn't create a new settings object but modifies the first one. But the (implicit) assumption of a method call is that I own the return value. Otherwise I'd have property semantics instead of method call semantics.

When does this go wrong? When I do this:

private static JsonSerializer GetCustomSerializer()
{
    // get the defaults and add your own special converter for this serializer
    var xxx = JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings();
    xxx.Converters.Add(new YDeserializer());
    return JsonSerializer.Create(xxx);
}

The effect is that the default settings now have the special converter too.

What I actually wanted was:

That's why I'd like a clone method (or a c++-style copy constructor) on the settings object.

Or is there a better way of doing this?

Here's a piece of sample code:

using System;
using Newtonsoft.Json;

namespace NewtonsoftGames
{
    class Program
    {

        static void Main(string[] args)
        {

            SetSettings();
            Console.WriteLine(JsonConvert.DefaultSettings().Converters.Count);

            SetAdditionalSettings();
            Console.WriteLine(JsonConvert.DefaultSettings().Converters.Count);

            var serializer = GetCustomSerializer();
            Console.WriteLine(JsonConvert.DefaultSettings().Converters.Count);

        }

        private static void SetSettings()
        {
            var xxx = JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings();
            xxx.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
            JsonConvert.DefaultSettings = () => xxx;
        }

        private static void SetAdditionalSettings()
        {
            var xxx = JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings();
            xxx.Converters.Add(new XDeserializer());
            JsonConvert.DefaultSettings = () => xxx;
        }

        private static JsonSerializer GetCustomSerializer()
        {
            var xxx = JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings();
            xxx.Converters.Add(new YDeserializer());
            return JsonSerializer.Create(xxx);
        }
    }

    public class XDeserializer : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => throw new NotImplementedException();
        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) => throw new NotImplementedException();
        public override bool CanConvert(Type objectType) => throw new NotImplementedException();
    }
    public class YDeserializer : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => throw new NotImplementedException();
        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) => throw new NotImplementedException();
        public override bool CanConvert(Type objectType) => throw new NotImplementedException();
    }
}