How to Save Objects to a File with JSON?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By lordbry

Hello all. I’m having a strange problem with saving game progress. I originally saved each variable separately to a file. That worked fine. There were SO MANY variables though, that every time I added a new one, or changed something around, it got too crazy to manage. So I tried to save an OBJECT instead. That way, I change the object class, and everything saves properly – in theory. It doesn’t work though. I’m missing a step somewhere and I hope you can help me with it. Here’s what I have:

Vars.gd:

extends Node

var MyVar = 0

func _init(_MyVar):
    MyVar = _MyVar

Main.gd

extends Node
var oVars = preload("res://Vars.gd")
var MyStuff = []
MyStuff.append(oVars.new("XYZ"))

Save.gd

extends Node

onready var myAutoLoad  = get_node("/root/Vars")
const FILE_NAME = "user://saves.json"

var env = {
    "varObj": []
}

func saveAll():
    env.varObj = myAutoLoad.MyStuff
    var file = File.new()
    file.open(FILE_NAME, File.WRITE)
    file.store_string(to_json(env))
    file.close()

Now when I look at what was actually saved, I see this:

saves.json

{"varObj": ["[Node:1355]"]}

I could have 1000 variables in there, and it still condenses them all into “Node:1355” instead of saving each variable in the object as a dictionary. Since it doesn’t save each variable independently, I can’t load it properly. :frowning: UGH!

Thanks in advance for your help with this issue.

:bust_in_silhouette: Reply From: kufyweruk
var pathSave="user://save_game_";

var passFile="fwegfuywe7r632r732fdjghfvjhfesedwfcdewqyhfewjf"
#Any number of variables of different types
var data:Dictionary={}


func saveGame(id):

	var file = File.new()

	var path=pathSave+id as String

	file.open_encrypted_with_pass (path, File.WRITE,passFile)
	file.store_var(to_json(data), true)
	file.close()
	pass;



func loadGame(id):
	var file = File.new()

	var path=pathSave+id as String

	if file.file_exists(path):
		
		file.open_encrypted_with_pass(path, File.READ,passFile)
		var loadParam=parse_json(file.get_var(true));
		file.close()
	
		if loadParam!=null:
			data=loadParam;
			pass;
	
	pass;

Hi. This does that same thing I’m already doing. The only change is the encryption. My save file still doesn’t have all the individual variables in it. :frowning:

lordbry | 2020-08-27 04:04

:bust_in_silhouette: Reply From: Guliver_Jham

The way you’re trying to do this is kinda strange, the way i save & load dictionaries tought is this:

func save(var path : String, var thing_to_save):
var file = File.new()
file.open(path, File.WRITE)
file.store_var(thing_to_save)
file.close()

and

func loadDictionary (var path : String) -> Dictionary:
var file = File.new()
file.open(path, File.READ)
var theDict = file.get_var()
file.close()
return theDict

I’m trying to save an object filled with variables instead of an individual variable. This way I can change the object as much as I want, without having to change my save code. Ideally, my save code will be “universal” so I just need to feed it any object filled with variables.

lordbry | 2020-08-27 04:01

:bust_in_silhouette: Reply From: Inces

It seems it is impossible to save whole objects like this. If You want to save all of the custom variables You create for own class, I would recommend using get_script_property_list() for that matter, it is an array of dictionaries containing only variables defined in scipt, and their values. If You want to save more, like built-in properties, than You can use normal get_property_list(). When You load it, You will need to create new instance of your node, and iterate through list with simple loop like:
var z = node.instance()
For x in loadedproperty_list :
z.set(x, loadedproperty_list)

All my variables are on object classes. For example, I have a player class, which holds the health, score, gold, etc. If I try to save that object, or load to it, the system won’t do it. I created those classes so I could have multiple players, and hopefully save them individually. Since those aren’t nodes, could I adapt this code for my purposes without having to restructure my entire data storage scheme?

lordbry | 2021-06-28 16:05

I’m sorry, but You simply can’t use objects as data holders for the purpose of loading. As You saw, the only thing saved this way is objects ID. But my sollution is only a bit more complicated than saving whole object - You are supposed to save this objects script property_list. This is a dictionary containing only custom variables You created for that object and their values, it looks like this { "health : 100, “gold” : 12342, “score” : 666} and so on. You don’t have to make it anew, it is already there built in your class script, so all You have to do is get() it, update keys for present values while saving, and than assign every key value to subsequent property after loading it.

This should be universal and save every piece of information about your node regardless of how many custom variables they have. However I do believe it is better to prepare own stable dictionary for storing variables.

Inces | 2021-06-28 20:23

:bust_in_silhouette: Reply From: stormreaver

Since this is an old thread, I’m sure you’ve already discovered this; but for those struggling, the answer is to use a dictionary (the original poster was using an array) in the object to store your data. Dictionaries are directly useable by to_json() and parse_json(), while arrays are not.

Do not store your data in individual variables, as you will then have to construct/deconstruct a dictionary manually for the save and load operations. To insulate your program from your internal data representation, use accessor methods that read/write your dictionary rather than directly referencing your dictionary components.

Using this approach, you can add and remove variable items without ever having to modify your save and load functions.

Hi. Thanks for your comment. I tried that approach and that didn’t work for me. It’s possible that my code was faulty though. Is there some sample code you could share for the initialization, the saving, and the loading?

lordbry | 2021-06-28 15:44

Here’s my core code for my Player class:

extends Node

var m_aData = {}

func getPassword():
	return m_aData["password"]

func setPassword(a):
	m_aData["password"] = a

func getData():
	return m_aData

func setData(a):
	m_aData = a

Add additional accessor methods to add support for additional fields. These will get and set the m_aData dictionary. The initial dictionary is empty, and automatically creates new entries when a set*() method references one that doesn’t already exist. This is the only file you will ever have to update when you add or remove fields from the Player class.

Reading and writing the JSON files will automatically read/write the new fields you add to Player, and you can follow this pattern for every persistent data type you want to add to your game.

Here’s how I save the dictionay to a JSON file:

func createAccount(stAccountname,stUsername,stPassword,stPath):
	print("Preparing new account")
	var player = load("res://player/Player.gd").new()
	var stData
	player.setAccountName(stAccountname)
	player.setPlayerName(stUsername)
	player.setPassword(stPassword)
	stData = player.getData()
	return ConfigurationWriter.write(stPath,to_json(stData))

ConfigurationWriter.write() is just a helper function that opens a file, writes the JSON text, and closes the file; with error handling built-in. You will never have to modify this function to add or remove Player data fields.

Here’s an example of reading the player data to validate the password:

func authenticatePassword(stPath,stPassword):
	var ret = false

	print("Authenticating password")
	if stPath != null && stPassword != null:
		var stData = ConfigurationReader.read(stPath)
		var aData
	
		if stData != null:
			aData = parse_json(stData)
			ret = aData["password"] == stPassword
		else:
			print("Can't read data from " + stPath)
	return ret;

Again, ConfigurationReader.read() is just a convenience method to open a file, read it, and return the raw text data (saved in JSON format).

stormreaver | 2021-06-28 16:22