Search code examples
c#.netjson-deserialization

How can I assign the property of class after deserializing the object?


I want to populate the splitterposition after deserializing the class.

 public class UserProfile{
        
        public int SplitterPosition { get; set; }

        public static readonly UserProfile Default = new UserProfile();

        public const string FileName = "UserProfileSettings.json";

        public UserProfile()
        {
            SplitterPosition = 313;
        }

        public static void Load()
        {
            if (File.Exists(Path.Combine(ProfileDirectory, FileName)))
            {
                UserProfile user = RoboostSerializer.DeserializeObject<UserProfile>(File.ReadAllText(Path.Combine(RoBoost.Common.FileSystemConstants.ProfileDirectory, FileName)));

                Default.SplitterPosition = user.SplitterPosition;

            }
        }

        public static void Save()
        {
           string serialized = RobotSerializer.SerializeObject(Default);

            if (File.Exists(Path.Combine(ProfileDirectory, FileName)))
            {
                File.WriteAllText(Path.Combine(ProfileDirectory, FileName), serialized);
            }
        }
    }

calling load method like this

UserProfile.Load();

but i can't figure out how to assign the prop without resetting its values


Solution

  • With the updated code, it looks like what you're really asking is:

    Can we do an in-place deserialize over an existing object, and have the serializer assign values to that object?

    This question is dependent on the specific serializer being used (which we can't see), but usually the answer is a simple "no"; most serializers do not support this use-case (some do). It looks like you're using JSON, and neither of the two most likely JSON serializers (Newtonsoft and STJ) support in-place deserialize, AFAIK. In reality, this does not present a problem, as you can:

    1. manually copy properties, as you're doing
    2. do an object swap, i.e. Whatever = user;

    This second option is usually preferable, because it can perform this atomically - meaning: there's never a hypothetical situation (usually involving multiple threads) where an instance is observed in a corrupt state where it has some values from the old/outgoing data, and some other values from the new/incoming data - this can be dangerous and lead to invalid operation. So, except for a huge caveat I'll get to in a moment, my recommendation would be to remove the readonly, and just do Default = user;.

    However (the caveat)! I am very concerned by:

    public static readonly UserProfile Default = new UserProfile();
    

    This kind of static usage needs to be considered very carefully, and is usually a bad idea. It is broadly tolerable in single-user applications, for example desktop GUI applications, but would be wildly inappropriate in a server application, for example a web server. Even in a single user application, to avoid the hybrid state problem, any usage would need to capture the instance once only, i.e. instead of:

    Write("User: " + UserProfile.Default.Id);
    Write("User: " + UserProfile.Default.Name);
    

    you'd need:

    var user = UserProfile.Default;
    Write("User: " + user.Id);
    Write("User: " + user.Name);
    

    By the time you've checked that for all existing code (remembering to keep checking that for every future code change), it is less work to simply pass in the appropriate user, rather than having ambient state. It might also be acceptable to change this slightly to be a method rather than a property/field:

    var user = UserProfile.GetDefaultUser();
    Write("User: " + user.Id);
    Write("User: " + user.Name);
    

    The significance here being that as a method, it "feels" more expensive, i.e. something you shouldn't do repeatedly; it would be less likely to see:

    Write("User: " + UserProfile.GetDefaultUser().Id);
    Write("User: " + UserProfile.GetDefaultUser().Name);
    

    simply because the GetDefaultUser() "looks" suitable expensive that most people would think to snapshot the return value once. This is not reliable, though - people often write obviously inefficient code, so I wouldn't rely on changing this to a method being a perfect way to prevent hybrid state.