Preamble: I know about ISerializable deprecation. We have to use it because we have massive legacy codebase for IPC (that uses this interface). We are migrating from .NET Remoting to a crossplatform solution. And we planned to keep existing codebase setup if it's possible.
I have a class library (with no extra dependencies) targeting .NET Standard 2.0, with some classes that implement ISerializable:
using System; using System.Runtime.Serialization; namespace DataClasses { [Serializable] internal sealed class User : ISerializable { public string Name { get; set; } public UserGroup Group { get; set; } internal User() {} private User(SerializationInfo info, StreamingContext context) { Name = (string)info.GetValue(nameof(Name), typeof(string)); Group = (UserGroup)info.GetValue(nameof(Group), typeof(UserGroup)); } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue(nameof(Name), Name); info.AddValue(nameof(Group), Group); } } [Serializable] internal sealed class UserGroup : ISerializable { public string Name { get; set; } public User DefaultUser { get; set; } internal UserGroup() { } private UserGroup(SerializationInfo info, StreamingContext context) { Name = (string)info.GetValue(nameof(Name), typeof(string)); DefaultUser = (User)info.GetValue(nameof(DefaultUser), typeof(User)); } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue(nameof(Name), Name); info.AddValue(nameof(DefaultUser), DefaultUser); } } } And I have a .NET 8.0 console application which references Newtonsoft.JSON and serializes/deserializes those classes (I use InternalsVisibleTo in class library project to make it work):
using Newtonsoft.Json; namespace NewtonsoftJSONTest { internal class Program { static void Main(string[] args) { var user = new DataClasses.User { Name = "User" }; var group = new DataClasses.UserGroup { Name = "Group", DefaultUser = user }; user.Group = group; var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; var json = JsonConvert.SerializeObject(user, settings); var userRestored = JsonConvert.DeserializeObject<DataClasses.User>(json); } } } With that config I get an exception on the line:
DefaultUser = (User)info.GetValue(nameof(DefaultUser), typeof(User)); System.Runtime.Serialization.SerializationException: Member 'DefaultUser' was not found.
Newtonsoft.JSON obtains SerializationInfo for an object but then doesn't include into json properties that are a reference loop.
The json is
{ "Name": "User", "Group": { "Name": "Group" } } so the code on line
DefaultUser = (User)info.GetValue(nameof(DefaultUser), typeof(User)); fails to find DefaultUser. I can implement a workaround extension method SerializationInfo.TryGetValue like that (since MS doesn't plan to add it), but is uses try catch and hence is slow.
Is there a way to get JSON with DefaultUser=null? Something like:
{ "Name": "User", "Group": { "Name": "Group", "DefaultUser": null } } A solution that does not use Newtonsoft.JSON attributes is highly appreciated as I don't want my class library to have external dependencies. Maybe some custom ContractResolver can help me?
Sample project repo.
UPDATE: Possible solution with PreserveReferencesHandling.Objects setting.
PS: with
ReferenceLoopHandling = ReferenceLoopHandling.Serialize flag it get this exception:
System.StackOverflowException: Exception of type 'System.StackOverflowException' was thrown.
PPS: looks like System.Text.Json with ReferenceHandler.IgnoreCycles option produces json that I need but it doesn't support ISerializable so is not suitable for me. Moreover System.Text.Json has its own drawbacks, for example it has no automatic type handling. I have to mark my base data classes and interfaces with JsonDerivedTypeAttribute. That 1) adds an external dependency to my class library (I try to avoid it) 2) is painfull as codebase is massive and hence inheritance tree also
ISerializableis obsolete in .NET 8, see SYSLIB0051: Legacy serialization support APIs are obsolete. So continuing with this serialization strategy is likely to be quite painful for you asBinaryFormatter-related APIs and types are either removed or made to throw exceptions. Why are you usingISerializable?ISerializabledeprecation. We have to use it because we have massive legacy codebase for IPC (that uses this interface). We are migrating from .NET Remoting to a crossplatform solution. And we planned to keep existing codebase setup if it's possible.