Search code examples
jsonvb.netwinformsjson.net

Set property of a nested JSON object


I'm new to JSON.Net and haven't been able to figure out how to get a nested object of my JSON as a JObject. I am using a file to store my JSON objects, and then write the changes I made back to the file.

Background

I am trying to make an app where the user selects a folder on their computer, and the app enumerates that folder and all of its files into a JSON object. The user can then select any of those files and set values of that file to be saved to the JSON file. So far I have made the function to generate the directory structure/contained files into JSON and save it to a file, but I am stuck at trying to then access a nested object of the JSON in order to set a property's value.

My code so far

The code used to generate the JSON and store it into the file is:

Private Sub btnAddFolder_Click(sender As Object, e As EventArgs) Handles btnAddFolder.Click

    'Create JObject from JSON File
    Dim json = File.ReadAllText(jsonFile)
    Dim obj As New JObject
    If json <> "" Then
        obj = JObject.Parse(json)
    End If

    'Add directory as a nested object of JSON
    ' - Add all of files in directory as nested objects of the directory object
    If obj.Property(txtDirectory.Text) Is Nothing Then
        'Object to store all files as nested objects
        Dim files As New JObject
        'Object to store all properties of a file object
        Dim fileInfo As New JObject
        'Variables to store properties for fileInfo
        Dim stringProp As String = "default"
        Dim arrProp() As String = {"default"}
        Dim jArrProp As JArray = JArray.FromObject(arrProp)

        'Add properties to objcet
        fileInfo.Add("var1", stringProp)
        fileInfo.Add("var2", jArrProp)

        'Add object of properties to each file object
        For Each file As String In Directory.GetFiles(txtDirectory.Text)
            files.Add(file, fileInfo)
        Next

        'Add each file object to directory object
        obj.Add(txtDirectory.Text, files)
    End If

    'Write JSON to file
    File.WriteAllText(jsonFile, JsonConvert.SerializeObject(obj, 1))
End Sub

The generated file/JSON looks like:

{
  "Directory1": {
    "File1": {
      "var1": "default",
      "var2": ["default"]
    },
    "File2": {
      "var1": "default",
      "var2": ["default"]
    }
  },
  "Directory2": {
    "File1": {
      "var1": "default",
      "var2": ["default"]
    },
    "File2": {
      "var1": "default",
      "var2": ["default"]
    }
  }
}

What I need next

The code above is called when a Button labeled Add Folder is clicked.
Another Button, labeled Select File and Set Data, prompts Users to select any file from the directory and input values that will be stored in var1 and var2 of that file.

For example, when the Select File and Set Data Button is clicked, a User selects File1, then writes "This file is a picture from my 2012 vacation to the islands." in a TextBox. I need my code to set var1 of obj.Directory1.File1 to the string value specified.
This is where I am stumped. I do not know how to use JSON.net to access a nested object in order to set a value of one of its properties.

I know that a JObject has a .Property().Value() method that can be used to set or get the value of a property, but since my intended JSON object is nested inside the main JSON object, I cannot figure out how to get it as a JObject in order to access that method. JObject.Item() returns a JToken which does not have a .Property().Value() method.


Solution

  • Your JSON structure contains Directories names or full paths, which are all different, so a class model is not really useful to describe the whole content of this JSON.
    Since a Directory path is unique (you cannot have two identical paths that refer to distinct Directory roots), the Directory path can be used as the Key of a Dictionary.
    It's also simple to determine whether the Dictionary already contains that Key (since you're checking this in your code), using the Dictionary.ContainsKey("Key") method.

    The same applies to the Files names: usually a Directory cannot contain files with identical names. So you can use the File Path as the Key of a second Dictionary, which is the Value of the first one (which, as described, has the Directory as Key).

    The var1 and var2 values are instead always the same, so you can use a class model to describe this structure. You can also assign default values to the properties (in different ways; here I'm just assigning a default value explicitly)

    Public Class FileObj
        <JsonProperty("var1")>
        Public Property Var1 As String = "default"
        <JsonProperty("var2")>
        Public Property Var2 As List(Of String) = New List(Of String)({"default"})
    End Class
    

    At this point, when you have deserialize your JSON, you just check whether the Dictionary already contains a Key equal to the new Directory path.
    If it does not, you add this new Key and a Dictionary(Of String, FileObj) as Value, where the Key is the File Name and FileObj is the class that contains your two properties:

    Load the already saved content, if any:

    Private DirectoryObjects As Dictionary(Of String, Dictionary(Of String, FileObj))
    
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim json = File.ReadAllText(jsonFile)
        DirectoryObjects = JsonConvert.DeserializeObject(
            Of Dictionary(Of String, Dictionary(Of String, FileObj)))(json)
    End Sub
    

    Add a new Directory structure to the current Dictionary:

    Private Sub btnAddFolder_Click(sender As Object, e As EventArgs) Handles btnAddFolder.Click
        Dim dirPath = txtDirectory.Text
        If DirectoryObjects.ContainsKey(dirPath) Then Return
    
        Dim fileInfo As New Dictionary(Of String, FileObj)()
        For Each file As String In Directory.GetFiles(dirPath)
            fileInfo.Add(file, New FileObj() With {
                .Var1 = "Something",
                .Var2 = New List(Of String)({"Something", "Else"})
            })
        Next
    
        DirectoryObjects.Add(dirPath, fileInfo)
    
        ' Now you can serialize and write to disc to preserve the changes
        Dim dirInfoJson = JsonConvert.SerializeObject(DirectoryObjects)
        File.WriteAllText(jsonFile, dirInfoJson)
    End Sub
    

    Now, you can use, e.g., a ComboBox - to select one of the Directories - and a ListBox / ListView - to show the content of the selected Directory.
    When one File is selected, the Directory is the Key of the outer Dictionary, the File the Key of the inner Dictionary.
    ► A Dictionary.ToList() can be used as the DataSource of most Controls that hold collection of items.

    • txtDescription.Text is the meant to represent the User input related to var1
    • txtDataVar2a.Text and txtDataVar2b.Text other inputs that represent the values of var2.
    Private Sub btnEditFileInfo_Click(sender As Object, e As EventArgs) Handles btnEditFileInfo.Click
        ' Get the inner Dictionary using the Directory as the Key of the outer Dictionary
        Dim fileObjects As Dictionary(Of String, FileObj) = DirectoryObjects(txtDirectory.Text)
        ' Get the FileObj object using the File as the Key of the inner Dictionary
        Dim fileInfo As FileObj = fileObjects("[Selected File Path]")
        ' Add the new Values to var1 and var2
        fileInfo.Var1 = txtDescription.Text
        fileInfo.Var2 = New List(Of String)({txtDataVar2a.Text, txtDataVar2b.Text})
    
        ' You can serialize the Dictionary now or do it later
        Dim dirInfoJson = JsonConvert.SerializeObject(DirectoryObjects)
        File.WriteAllText(jsonFile, dirInfoJson)
    End Sub
    

    You'll see that this JSON structure is identical to the one you're showing here.

    Note:
    There's the chance that File names in new File Systems are case sensitive, but this is not a problem because our string comparison is also case sensitive.