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.
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.
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"]
}
}
}
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.
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.