How to organize JSON Save File

Godot Version

4.3

Question

I am working on creating a save system for my project and I have created a basic save system to save my players inventory.

extends Node

const SAVE_PATH: String = "res://savegame.bin"

func save_inventory() -> void:
	var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
	var inventoryData: Dictionary = {}
	var inventoryGrid = GUI.inventoryMenu.get_child(0).get_child(1)
	for i in inventoryGrid.get_child_count():
		var slot_index = i
		var item_data
		var item_data_count
		if inventoryGrid.get_child(i).get_child_count() <= 0:
			item_data = null
			item_data_count = 0
			inventoryData[slot_index] = {item_data: item_data_count}
		elif inventoryGrid.get_child(i).get_child_count() > 0:
			#slot has an item
			item_data = inventoryGrid.get_child(i).get_child(0).data
			item_data_count = inventoryGrid.get_child(i).get_child(0).itemCount
			inventoryData[slot_index] = {item_data.itemName: item_data_count}
	var inventoryJSTR = JSON.stringify(inventoryData)
	file.store_line(inventoryJSTR)
	print("saved")

func load_inventory() -> void:
	var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
	if !file:
		return
	if file == null:
		return
	if FileAccess.file_exists(SAVE_PATH) == true:
		if not file.eof_reached():
			var current_line = JSON.parse_string(file.get_line())
			if current_line:
				var inventoryGrid = GUI.inventoryMenu.get_child(0).get_child(1)
				for index in current_line:
					for item in current_line[index]:
						if item != str("<null>"):
							var inventoryItem = InventoryItem.new()
							inventoryItem.init(Game.items[item])
							if inventoryGrid.get_child(int(index)).get_child_count() <= 0:
								inventoryGrid.get_child(int(index)).add_child(inventoryItem)
								inventoryGrid.get_child(int(index)).get_child(0).itemCount = int(current_line[index].get(item))
				print("loaded")

Example of Save File:

{“0”:{“”:0},“1”:{“”:0},“2”:{“”:0},“3”:{“”:0},“4”:{“Purple Berry”:5},“5”:{“”:0},“6”:{“”:0},“7”:{“”:0},“8”:{“”:0},“9”:{“”:0},“10”:{“”:0},“11”:{“”:0},“12”:{“”:0},“13”:{“Purple Berry”:4},“14”:{“”:0},“15”:{“”:0},“16”:{“”:0},“17”:{“”:0},“18”:{“”:0},“19”:{“”:0},“20”:{“”:0},“21”:{“”:0},“22”:{“”:0},“23”:{“”:0},“24”:{“”:0},“25”:{“”:0},“26”:{“Purple Berry”:6},“27”:{“”:0},“28”:{“”:0},“29”:{“”:0}}

My question is, if I wanted to store more lines of information to be saved; for example to save the hotbar inventory or equipment slots, what is the best way to store multiple lines in a JSON file? Or how should I organize the save file so i can easily get the line i need to load and not need to keep track which line is what in the JSON? Can I create a label or a tag for each line to easily get what i need?

The way I do it personally is with ConfigFile and sections

extends Node


var _file: ConfigFile = ConfigFile.new()
const _filename: String = "user://file.cfg"


var _data_array: Array = []


func _ready():
	_file.load(_filename)
	
	_init_data_array()


# _data_array
func _init_data_array():
	set_data_array(_file.get_value("SECTION_NAME", "data_array", []))

func get_data_array() -> Array:
	return _data_array

func set_data_array(val: Array = []):
	_data_array = val
	_file.set_value("SECTION_NAME", "data_array", val)
	_file.save(_filename)

func append_data_array(val: String):
	_data_array.append(val)
	set_data_array(_data_array)

This way when the node is loaded it updates _data_array and by using the get_ set_ append_ functions keep both the variable and the file up to date.

The _data_array here is an example of course, it works for basic data types, here is what the file looks like:

[Creatures]

discovered=["mob_1", "mob_2"]

[Tutorial]

finished=true

[Inventory]

items=["item_1", "item_2", "item_3"]
hotbar=["item_1"]

To be clear for the Inventory section the code would be

# INVENTORY
# _items_array
func _init_items_array():
	set_items_array(_file.get_value("Inventory", "items", []))

func get_items_array() -> Array:
	return _items_array

func set_items_array(val: Array = []):
	_items_array = val
	_file.set_value("Inventory", "items", val)
	_file.save(_filename)

func append_items_array(val: String):
	_items_array.append(val)
	set_items_array(_items_array)

# _hotbar_array
func _init_hotbar_array():
	set_hotbar_array(_file.get_value("Inventory", "hotbar", []))

func get_hotbar_array() -> Array:
	return _hotbar_array

func set_hotbar_array(val: Array = []):
	_hotbar_array = val
	_file.set_value("Inventory", "hotbar", val)
	_file.save(_filename)

func append_hotbar_array(val: String):
	_hotbar_array.append(val)
	set_hotbar_array(_hotbar_array)

This needs a lot of boilerplate code, but once it’s done it’s quite robust and the auto-complete in-editor is quite nice.

This also allows to add custom code for some sections (for exemple in the get_items_array() to return the actual items instead of a String array).

If You want to keep the JSON format, calling get_line() again on a FileAccess:file will get you the next line.


func load_inventory() -> void:
	var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
	if !file:
		return
	if file == null:
		return
	if FileAccess.file_exists(SAVE_PATH) == true:
		if not file.eof_reached():
			var current_line = JSON.parse_string(file.get_line())
			if current_line:
				pass #INVENTORY
			current_line = JSON.parse_string(file.get_line())
			if current_line:
				pass #HOTBAR
			current_line = JSON.parse_string(file.get_line())
			if current_line:
				pass #EQUIPMENT_SLOTS

The reason I mentioned the section system in the other post is that the system you have seem like it would be a little hard to update, but I am sure with a bit of structuring you will also be able to manage :grinning:


Also: you can store it all in a greater dictionary, so

{"inventory": {“0”:{“”:0},“1”:{“”:0},...},"hotbar": {...},"equipment": {...}}

then

func load_save() -> void:
	var file = FileAccess.open(SAVE_PATH, FileAccess.READ)

	if !file or file == null:
		return

	var json_save = JSON.parse_string(file.get_line())
	
	if !json_save:
		return

	load_inventory(json_save["inventory"])
	load_hotbar(json_save["hotbar"])
	load_equipment(json_save["equipment"])