require "json"
io = File.open("a.json")
db_builder = JSON::Builder.new io
db_builder.document do
db_builder.object do
db_builder.field "fooo", 123
end
end
File.write("a.json", io)
It doesn’t work, any ideas on how I would do this? I’m attempting to load a JSON db (for a small private app) and change it’s properties + read it
File.open defaults to a read mode. You should probably use `File.open(“a.json”, “w”).
Also, are you receiving an error message or is this code silently failing?
Hey, Thank you for the quick response, the code doesn’t fail/error it just doesn’t write anything, the file stays blank, I tried to do
File.open("a.json", "w")
and got
Unhandled exception: Error reading file: Bad file descriptor (Errno)
1 Like
So, while the file mode was an issue, you also need to change the final line to
io.close
The JSON::Builder
automatically writes to the IO
it’s given, so you just need to close io
to finish the writing.
It’s nice to close your file descriptors, but most of the time it’s not a requirement, OS will do it for you upon the process exit.
The problem here is that you use two different approaches at the same time, and they conflict, leaving you in an unconsistent state, which happens to manifest as an empty file in your case, but seems to me as an undefined behaviour in general.
Let’s inspect your code in English, much simplified:
- Open a file named “a.json”, and have its instance in a variable named
io
- Construct a string, and write it out to a file that we have open in a variable
io
- Open a file named “a.json”, and write to it the contents of a variable
io
Just remove the last line and your problem should go away, but keep , "w"
bit.
2 Likes
Hey, I’ve removed the last line but it still doesn’t appear to work, the file stays blank
This works as expected:
require "json"
io = File.open("a.json", "w")
db_builder = JSON::Builder.new io
db_builder.document do
db_builder.object do
db_builder.field "fooo", 123
end
end
io.close
Ay! it worked! thank you very much! and for you too @stronny
1 Like
For an alternate approach, you can write your JSON to a String::Builder
and then write it out as follows:
require "json"
io = String::Builder.new
db_builder = JSON::Builder.new io
db_builder.document do
db_builder.object do
db_builder.field "fooo", 123
end
end
File.write("a.json", io.to_s)
I don’t know if there are significant performance differences between the two approaches, though.
1 Like
For yet another approach you may find helpful to do away with manual builder altogether:
require "json"
struct DB_Object
JSON.mapping fooo: Int32
def initialize(@fooo : Int32); end
end
File.open "a.json", "w" { |io| DB_Object.new(123).to_json io }
1 Like
I came across another issue tho, whenever I restart the program the file resets and everything in it gets deleted
Oh, if you want to append to the file, you need to use the “a” file open mode. You can read more about those modes here.
So this is not an issue specific to Crystal in any way. To save you some trouble, maybe you can describe your problem in broad terms and we can offer an advice? Files are hard, you probably don’t want to deal with them directly, especially if you have a word “database” anywhere near.
1 Like
But then if the file has
{
"property": "val"
}
the next run will make it
{
"property": "val"
}
{"another": "anotherval"}
How come it’s being off topic? I want to know how can I modify/add JSON file’s properties.
How would you do that in any other language? A simple problem with File
class may unwind to become a serious architecture question.
1 Like
Do you want me to tell you how easy I can work with files using Javascript or Python?
No idea about JS, but it should not be all that different from Python, with the main new thing being blocks.
In short I don’t understand what is the nature of the problem you’re having.
- Read a file into your class
- Modify the data
- Write the data out to the file
There is nothing hard about individual file operations, files are hard to do right on the system level. Atomic updates, kill -9, corruption recovery, all that.
Ok then I think I’ve got an idea.
Here’s a minimal working example:
require "json"
struct DB_Object
JSON.mapping fooo: {type: Int32, setter: true}
def initialize(@fooo : Int32); end
end
filename = "a.json"
init_value = 123
db = DB_Object.new init_value
begin
File.open filename do |io| db = DB_Object.from_json io end
db.fooo += 1
rescue x : Errno
raise x unless Errno.value.is_a? Errno::ENOENT
end
File.open filename, "w" { |io| db.to_json io }
$ cat a.json
cat: a.json: No such file or directory
$ ./test3.cr
$ cat a.json
{"fooo":123}$ ./test3.cr
$ cat a.json
{"fooo":124}$ ./test3.cr
$ cat a.json
{"fooo":125}$