2

I have been investigating this exception for a while in my UWP C# project and I am having trouble figuring out exactly what is causing it. I would appreciate a solution, or even some tips on how to troubleshoot beyond what I have done so far.

I recently updated my app to serialize JSON using Newtonsoft.Json instead of System.Text.Json and while it runs, the Debug session breaks later on (VS catches this one, the whole app doesn't crash) due to the exception.

The exception does not occur when I run .Save() after starting the application. This occurs specifically after I upload some images into my app (I'll share some code below) and then click on a button that calls ContinueBtn_Click, which seems related to my exception.

I have been combing through it, but I can't find anything that seems to cause an endless loop. I set breakpoints and outputs in my code, confirming that .Save() as well as ContinueBtn_Click() themselves only run once.

To start, here is the screenshot of the error:

Stack Overflow Exception screenshot (VS2022)

Here is the UserData class. I commented out the References lines in serializerDefault, but the exception occurs even if those are present.

public class UserData
    {
        public List<CMSession> userSessions { get; set;}
        public DatabaseConnection lastDatabaseConnection { get; set;}
        
        [JsonIgnore]
        JsonSerializerSettings serializerDefaults = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            //PreserveReferencesHandling = PreserveReferencesHandling.All,
            //ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
        };

        public UserData() { userSessions = new List<CMSession>(); }

        /// <summary>
        /// Saves the UserData to a serialized user.json file.
        /// </summary>
        /// <param name="devBreak">If debugging, may provide optional True value to break the debugger prior to save, allowing data manipulation.</param>
        public async Task Save(bool devBreak = false)
        {
            if (devBreak)
            {
                Debug.WriteLine("Saving with Break Mode. You may manipulate UserData in Immediate Window now, otherwise Continue.");
                Debug.Write("");
            }
            try
            {
                StorageFile udFile = await ApplicationData.Current.LocalFolder.GetFileAsync(@"UserData\user.json");
                var udData = JsonConvert.SerializeObject(this, serializerDefaults);
                await Windows.Storage.FileIO.WriteTextAsync(udFile, udData);
                Debug.WriteLine("User data was manually saved.");
            } catch(Exception ex)
            {
                if (App.cmDebugger.debug)
                {
                    Debug.WriteLine("Failed to serialize user data to file: " + ex.Message);
                }
            }
        }
    }

Here is my page's code-behind method that is calling .Save(), ContinueBtn_Click():

// Clicked after user drops some files into a box, the files are processed then added to pendingInit (a List<WorkingImage>)
private async void IUInit_ContinueBtn_Click(object sender, RoutedEventArgs e)
        {
            if (pendingInit.Count > 0)
            {
                 for (int i = 0; i < pendingInit.Count; i++)
                {
                    try
                    {
                        App.cmSession.IUSession.workAreaImages.Add(pendingInit[i]);
                    }
                    catch (Exception ex)
                    {
                        if (App.cmDebugger.debug)
                        {
                            Debug.WriteLine("Failed to move pending file to work area: " + ex.Message);
                            try { Debug.WriteLine("Image: " + pendingInit[i].fileName); } catch { }
                        }
                    }
                }
                IUState = DataState.RefreshNeeded;
                App.cmSession.IUSession.sessionActive = true;
                // This appears to be where the exception occurs. 
                await App.cmData.Save();
                await InitializeIU();
            }
            else
            {
                Init_Grid.Visibility = Visibility.Collapsed;
            }

            IUState = DataState.RefreshNeeded;
        }

Here is the JSON that is being generated successfully the first few times .Save() is called:

{
  "userSessions": [
    {
      "LinkedSite": {
        "hasContentAccess": true,
        "hasMediaAccess": true,
        "lastResponse": " (some JSON in here)",
        "friendlyName": "website",
        "URL": "http://www.google.com/",
        "username": "usr",
        "status": "connected",
        "statusDetail": "Connected to the site without errors.",
        "lastConnectionTime": "2023-05-10T15:49:24.0182548-05:00"
      },
      "IUSession": {
        "sessionActive": false,
        "workAreaImages": []
      }
    }
  ],
  "lastDatabaseConnection": null
}

At the risk of making the question a bit long, I'm also providing relevant data structures that are serialized in .Save():

CMSession:

public class CMSession
    {
        public SiteConnection LinkedSite { get; set; }
        public ImageUploaderData IUSession {  get; set; }

        public CMSession(SiteConnection linkedSite)
        {
            LinkedSite = new SiteConnection(linkedSite);
            IUSession = new ImageUploaderData();
        }
    }

DatabaseConnection:

    public class DatabaseConnection
    {
        public string dbName = database; 
        public string dbstate;
        public MySqlConnection connection;    
    }

SiteConnection:

    public class SiteConnection
    {
        public string friendlyName { get; set; }
        public string URL { get; set; }
        public string username { get; set; }
        public string status { get; set; }
        public string statusDetail { get; set; }
        public DateTime lastConnectionTime { get; set; }
        public bool hasContentAccess;
        public bool hasMediaAccess;
        public object lastResponse;
    }

ImageUploaderData:

    public class ImageUploaderData
    {
        public bool sessionActive {  get; set; } = false;
        public List<WorkingImage> workAreaImages {  get; set; } 
           = new List<WorkingImage>();
    }
Mark L
  • 83
  • 7
  • 2
    Does UserSession have a reference to UserData? Perhaps it's the JSON serialization that is infinitely referencing itself somehow – barrett777 May 10 '23 at 22:41
  • CMSession does not have a reference to UserData. I can't find a reference to self anywhere in that context. – Mark L May 11 '23 at 16:38

2 Answers2

2

Not 100% on this, but I think your error is only appearing when you're uploading images is telling. I suspect you're trying to serialize the image to json.

This is also backed by your example json having an empty "workAreaImages": [] when it's succeeding.

User data has a list of CMSession

    public UserData() { userSessions = new List<CMSession>(); }

CMSession has a IUSession of type ImageUploaderData

public class CMSession
{
    public SiteConnection LinkedSite { get; set; }
    public ImageUploaderData IUSession {  get; set; }

    public CMSession(SiteConnection linkedSite)
    {
        LinkedSite = new SiteConnection(linkedSite);
        IUSession = new ImageUploaderData();
    }
}

ImageUploaderData has a list of WorkingImage

public class ImageUploaderData
{
    public bool sessionActive {  get; set; } = false;
    public List<WorkingImage> workAreaImages {  get; set; } 
       = new List<WorkingImage>();
}

So when you try to serialize your UserData, it's failing when it finds an image.

Now if this WorkingImage does represent an image, then I don't know how Json.Net is trying to handle that during serialization, I suspect it's trying to do something similar to in this question JSON.NET StackOverflowException while serialization behind the scenes

However, I think this answer will actually help you write a custom json serializer for an image https://stackoverflow.com/a/44370397/9822528

DubDub
  • 1,277
  • 1
  • 10
  • 24
  • 1
    Also, I assume this worked before you switched. I don't know how System.Text.Json was handling your images, but if you write your own serializer, the images that were serialized previously and saved may not then load with your new serializer. – DubDub May 10 '23 at 23:02
  • 1
    I found the problem, surprisingly the images/files were not the issue, but a SolidColorBrush... I didn't accept this answer because it didn't solve my question (to be fair, I hadn't included the WorkingImage class), but I dropped you an upvote for pointing me in the right direction. Thanks! – Mark L May 11 '23 at 17:58
  • 1
    No worries, glad you managed to fix it. – DubDub May 12 '23 at 09:05
0

I have identified the issue! Props to @DubDub for pointing me in the direction of "workAreaImages" as a possible issue.

Here is what I did to identify the issue and resolve:

  1. I added a [JsonIgnore] property to all non-primitives in my data objects being serialized. Rebuilt the app and confirmed that it was able to serialize without these suspect objects.
  2. I started removing [JsonIgnore] from various object types, rebuilding the app and testing between each removal until I encountered the problematic object/type, also monitoring my JSON file each time for anything strange/not serializing.
  3. I eventually narrowed it down to a SolidColorBrush. When I removed JsonIgnore from this type, the application would crash.

The object itself wasn't used in my code in a way that should cause a circular reference (it is just a simple reference to a brush), so I am assuming it may be an issue with the type itself (e.g. Newtonsoft.Json doesn't know how to handle this) and will either JsonIgnore it permanently or write a converter/error handler to process it during serialization.

[JsonIgnore]
public SolidColorBrush tagColor;
Mark L
  • 83
  • 7