Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON Reference Loop #105

Closed
illumen opened this issue Jul 19, 2018 · 15 comments · Fixed by #155
Closed

JSON Reference Loop #105

illumen opened this issue Jul 19, 2018 · 15 comments · Fixed by #155

Comments

@illumen
Copy link

illumen commented Jul 19, 2018

Hi,

I'm using EFCore in my project together with dotNetify and I'm having issues with the JSON serialization.
Due to the navigation properties in my models, whenever I use them in my view models I quickly run into a reference loop.

Now, in a normal SignalR setup, I'd just configure it:
services.AddSignalR().AddJsonProtocol( options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; });

However, I can't manage to do this with dotNetify. Is there a way to configure the json serializer?

Regards

@dsuryd
Copy link
Owner

dsuryd commented Jul 19, 2018

I think either you clean up the data from any reference loop before passing it to the view model (better imho), or you would have to override the entire serialize method:

public override string Serialize(object instance, List<string> ignoredPropertyNames)

Use the original implementation here and modify as needed.

In any case, it would seem like a good idea to add that settings by default to dotNetify in the next release.

@illumen
Copy link
Author

illumen commented Jul 23, 2018

Yep, seems like cleaning up is the better choice.
However, isn't the VMSerializer a sealed class and thus can't be overridden?

@dsuryd
Copy link
Owner

dsuryd commented Jul 23, 2018

The link was only for reference; if you go the custom serializer route, you need to provide your own implementation when overriding BaseVM Serialize method.

@illumen illumen closed this as completed Jul 25, 2018
@lilasquared
Copy link
Contributor

@dsuryd not sure if i should open a separate issue for this. I was just wondering if it was possible to just override the casing of the serializer? I am trying to integrate this into an existing client app and all the javascript references to state are in lowerCamelCase. Is this possible without overriding the entire serializer?

@dsuryd
Copy link
Owner

dsuryd commented Nov 20, 2018

We can add similar AddJsonProtocol extension method to IDotNetifyConfiguration to override VMSerializer.cs line 47. Would you like to do a PR for this?

@lilasquared
Copy link
Contributor

Sure I would be happy to work on it. I am not 100% sure where the override would happen currently. since everything is new()'d up in VMSerializer. Would we override the jsonSerializer all together or just the ContractResolver?

something like this maybe?

public sealed class VMSerializer : ISerializer, IDeserializer
{
    public static JsonSerializer DefaultSerializer { get; set; } = new JsonSerializer { ContractResolver = new VMContractResolver(ignoredPropertyNames) } ;
    ...

    public string Serialize(object viewModel, List<string> ignoredPropertyNames)
    {
        ...
        var serializer = DefaultSerializer;
        ...
    }
}

then have the extension method set the DefaultSerializer?

or do you have a different idea in mind. I'm up for anything just need a little direction, thanks!

@dsuryd
Copy link
Owner

dsuryd commented Nov 20, 2018

Let's provide JsonSerializerSettings override and use JsonSerializer.Create(settings) to instantiate the serializer. If the override is provided and the ContractResolver is null, we need to set it with VMContractResolver.

@lilasquared
Copy link
Contributor

Hmm. What do we lose by overriding and not using VMContractResolver? How critical is the ignoredPropertyNames functionality? I guess since the property names won't match up they can't be ignored?

@dsuryd
Copy link
Owner

dsuryd commented Nov 20, 2018

VMContractResolver is not only used for that; also used for reactive properties and commands.

@lilasquared
Copy link
Contributor

did a quick test and setting the NamingStrategy inside the constructor of VMContractResolver works nicely. So if we allow VMContractResolver to be public, then I can inherit from it and set the NamingStrategy and use a configuration method to override the JsonSerializerSettings

it would look something like this

public class MyContractResolver : VMContractResolver
{
    public MyContractResolver(List<String> ignoredPropergyNames) : base(ignoredPropergyNames)
    {
        NamingStrategy = new CamelCaseNamingStrategy();
    }
}
...
app.UseDotNetify(config =>
{
    config.UseJsonSerializerSettings(ignoredPropertyNames => new JsonSerializerSettings
    {
        ContractResolver = new MyContractResolver(ignoredPropertyNames)
    });
});

here I have a factory method similar to VMController that by default is used to construct the settings with VMContractResolver. I needed this in order to have access to the ignoredPropertyNames.

/// <summary>
/// Delegate to create serializer settings.
/// </summary>
/// <param name="type">Class type.</param>
/// <param name="args">Optional constructor arguments.</param>
/// <returns>Object of type T.</returns>
public delegate JsonSerializerSettings CreateSerializerSettings(List<string> ignoredPropertyNames);

internal static CreateSerializerSettings CreateSettings = ignoredPropertyNames => new JsonSerializerSettings
{
    ContractResolver = new VMContractResolver(ignoredPropertyNames)
};

then using the method on the configuration object just overrides this factory method.

I appreciate any feedback!

@dsuryd
Copy link
Owner

dsuryd commented Nov 21, 2018

To make the API cleaner and simplify things, let's move the ignoredPropertyNames out of the constructor and make it a property instead. In the Serializer method, it will set that property if the serializer.ContractResolver can be casted to VMContractResolver.

@dsuryd dsuryd reopened this Nov 24, 2018
@dsuryd
Copy link
Owner

dsuryd commented Nov 27, 2018

I'm getting ready to publish the next version update. Do you think you can submit your PR by Wednesday?

@parksj10
Copy link

@dsuryd I'm not sure whether to open a new issue, but I'm trying to do essentially the same thing as the OP, namely something like:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
     Formatting = Newtonsoft.Json.Formatting.Indented,
     ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};

I have a database with a lot of virtual interfaces that cause this circular loop in many places, so I'd really need a global fix, is there anyway besides the ignoredPropertyNames?

@dsuryd
Copy link
Owner

dsuryd commented Oct 25, 2022

Thanks to the PR that came out of this issue, the default serializer settings can be customized as below:

app.UseDotNetify(config => {

   config.UseJsonSerializerSettings(settings =>  {
      settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
  }
}

@parksj10
Copy link

@dsuryd, thanks a ton, worked like a charm! (p.s. for others you have to put using Newtonsoft.Json; for ReferenceLoopHandling)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants