String to JSON issue

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

(go to bottom for tl;dr, and sorry about the italicization, idk how to fix that either)
Hi,

I’ve been learning Godot and gdscript for a few weeks now for my school project, and I definitely feel the improvement, but I still have one major hurdle I’m struggling to overcome. Right now I’m working on a part of a app my group is developing, and we want to use a JSON file to save the data even when the program is closed. So far I’ve figured out the basics of saving and loading to my JSON file (words.json), but my main issue comes with editing and saving it. When I try to edit my JSON, my first idea was to use strings (using this link)

and store it that way, but it didn’t work. It would leave slashes that I assume are meant to be for escape characters since they are next to the quotes.


My code’s purpose:
Basically I have some LineEdit nodes (word_input and def_input) where you enter the necessary fields for making a dictionary for each word. Then you press the out_button Button node to run make_dict() and make new_dict store the data in the LineEdit text fields. Then, you press the append_button Button node to add new_dict to words.json.


Here is some info about some of the variables:

  • new_dict: just an empty dictionary that will be filled with data to be appended to the JSON
  • data: dictionary variable where the JSON data gets loaded into to be interactable with the script and saved from.

Here is my code:

words.json (placeholder dictionaries, how the json starts originally):

{
"words": [
	{
	"word": "word1",
	"def": "definition1",
	"date": 1
	},
	{
	"word": "word2",
	"def": "definition2",
	"date": 2
	}
]

}

words.json(after writing append_dict(new_dict):

"{words:[{ \"word\": \"word1\", \"def\": \"definition1\", \"date\": 1 }, { \"word\": \"word2\", \"def\": \"definition2\", \"date\": 2 }, { \"word\": \"a\", \"def\": \"b\", \"date\": 3 }]}"

My functions:

check():

func check(): # checks if LineEdit nodes have text fields filled out
  if (%word_input.text.length() > 0 && %def_input.text.length() >0):
	  return true
  else:
	  return false

make_dict():

func make_dict(): # fills new_dict with the data from the LineEdit texts
  var temp_dict
  if (check() == false):
    	  return
  else:
	  temp_dict = {"word":%word_input.text, "def":%def_input.text, "date":3}
	  return temp_dict

append_dict(temp_dict):

func append_dict(temp_dict : Dictionary): #appends temp_dict (which would be new_dict) when a button is pressed) to data
	  if (check() == false):
		  %OUTPUT.text = "no input, try again."
	  else:
		  data = rewrite_json(temp_dict)

rewrite_json(temp_dict):

func rewrite_json(temp_dict : Dictionary): #supposed to return the newly edited json as a string
  var initial_data_str = data["words"]
  var arr = initial_data_str
  arr.append(temp_dict)
  var s = "{words:"+str(arr)+"}"
  return s

After loading the JSON through a not shown function, setting new_dict to make_dict(), and executing append_dict(new_dict), in that order, I get the second example of words.json. I tried to use parse_json(), but it only kept returning nulls. I’m working in Node2D, and essentially all of my code is in main.gd, if that’s important. Any help would be greatly appreciated and thanks in advance!


TL;DR to those who don’t want to invest to reading heavily and instead get a general gist before tackling the problem:

I want to turn my dictionary into a string similar to this question (but for godot 4.0.2):

However, JSON.parse() keeps returning nulls and doing it in other ways returns it as a string with a bunch of slashes. I want to return it as a dictionary to put it in my JSON, but I also want to append to it.

I’m working in Node2D, and essentially all of my code is in main.gd, if that’s important. Any help would be greatly appreciated and thanks in advance!

You can turn a variant into a JSON string using JSON.stringify()

JSON — Godot Engine (4.0) documentation in English

umaruru | 2023-05-25 17:00

:bust_in_silhouette: Reply From: Tom Mertz

If you take your JSON and paste it into a formatter/validator (you have to remove the outer " because those are Godot’s way of saying it’s a string.) your JSON fails the validator. This is because of the extra backslashes \. If you remove those you have valid JSON. Which might be why your parse is failing if your file indeed looks like the JSON you posted.

Your issue might be rewrite_json line 5. var s = "{words:"+str(arr)+"}" You should probably format that into a dictionary to have the same structure you want your json to be, then stringify that:

var s = { "words": arr }
return JSON.stringify(s)

In general, I like to keep my state into a dictionary for as long as possible and then with a save method stringify it all into JSON right on the spot and save into a file. That might look like

extends Node

var data: Dictionary = { "items": [ /* starting data here */ ] }
# ...
func _ready() -> void:
    load_json()

func load_json() -> void:
    # ... look for file and if we have one get contents
    data = JSON.parse_string(contents)

func save_json() -> void:
    var json = JSON.stringify(data)
    # ... Take json and save it into a file

func add_data_item(item) -> void:
    data.items.append(item)
    save_json()

Note the line from the docs here:

Attempts to parse the json_string provided and returns the parsed data. Returns null if parse failed.

You haven’t showed us any parsing code (or maybe I missed it), but from just that line it seems like you may be using that function and it’s erroring. (probably because of the backslashes \)

With the method above you’d only load json into your memory once when you start your game, then store everything in your Scene’s state until you need to add another item and then keep updating your json as you do it to mirror your Dictionary. Then everything is backed up and ready to be loaded when you need it, replacing the whole data Dictionary again.