86

I'm trying to create a function that would add entries to a json file. Eventually, I want a file that looks like

[{"name" = "name1", "url" = "url1"}, {"name" = "name2", "url" = "url2"}]

etc. This is what I have:

def add(args):
    with open(DATA_FILENAME, mode='r', encoding='utf-8') as feedsjson:
        feeds = json.load(feedsjson)
    with open(DATA_FILENAME, mode='w', encoding='utf-8') as feedsjson:
        entry = {}
        entry['name'] = args.name
        entry['url'] = args.url
        json.dump(entry, feedsjson)

This does create an entry such as {"name"="some name", "url"="some url"}. But, if I use this add function again, with different name and url, the first one gets overwritten. What do I need to do to get a second (third...) entry appended to the first one?

EDIT: The first answers and comments to this question have pointed out the obvious fact that I am not using feeds in the write block. I don't see how to do that, though. For example, the following apparently will not do:

with open(DATA_FILENAME, mode='a+', encoding='utf-8') as feedsjson:
    feeds = json.load(feedsjson)
    entry = {}
    entry['name'] = args.name
    entry['url'] = args.url
    json.dump(entry, feeds)
martineau
  • 119,623
  • 25
  • 170
  • 301
Schiphol
  • 1,101
  • 1
  • 9
  • 14
  • 4
    You aren't even using `feeds` in the second block, so of course you will lose the previous output. – nneonneo Oct 21 '12 at 02:36
  • 1
    Oh, man. Of course. I'm apparently too tired :( – Schiphol Oct 21 '12 at 02:39
  • [enter link description here](https://stackoverflow.com/questions/30350450/how-to-add-element-to-json-list-python/30350531) Maybe you could check out this question. It's easier to add new data in a list and you would not break your JSON format. – Drake .C Mar 06 '19 at 23:59

12 Answers12

90

json might not be the best choice for on-disk formats; The trouble it has with appending data is a good example of why this might be. Specifically, json objects have a syntax that means the whole object must be read and parsed in order to understand any part of it.

Fortunately, there are lots of other options. A particularly simple one is CSV; which is supported well by python's standard library. The biggest downside is that it only works well for text; it requires additional action on the part of the programmer to convert the values to numbers or other formats, if needed.

Another option which does not have this limitation is to use a sqlite database, which also has built-in support in python. This would probably be a bigger departure from the code you already have, but it more naturally supports the 'modify a little bit' model you are apparently trying to build.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • 2
    Thanks. I am trying to code a little podcast aggregator. Isn't a sqlite database a bit overkill for what will likely be 8-10 lines of feed data? – Schiphol Oct 21 '12 at 03:13
  • 3
    If your data is uniform, you can append to a json-like file, just have each line contain the object you want. Then when you read, just parse each line as a json object. – Jonno_FTW Aug 18 '17 at 05:22
  • @Jonno_FTW: using a file in that format would be quite a bit more bother than sqlite; In the former case, you need to accumulate all of the objects into a useful structure. Though that would only be half a dozen lines of python, that's still about a half dozen more than the 1 line needed to deal with sqlite or CSV. – SingleNegationElimination Aug 18 '17 at 12:01
  • 2
    @SingleNegationElimination this assumes that your data is uniform and fits nicely into a SQL table. Plus with json, you get more types than CSV's strings. Plus you can load the whole thing in with `[json.loads(i) for i in open('somefile.json','r').readlines()]` – Jonno_FTW Aug 24 '17 at 01:20
  • 1
    it is *worked* for me, you can use [jsonlines](https://jsonlines.readthedocs.io/en/latest/) package for writing and reading on json file – hassanzadeh.sd Dec 21 '19 at 13:53
49

You probably want to use a JSON list instead of a dictionary as the toplevel element.

So, initialize the file with an empty list:

with open(DATA_FILENAME, mode='w', encoding='utf-8') as f:
    json.dump([], f)

Then, you can append new entries to this list:

with open(DATA_FILENAME, mode='w', encoding='utf-8') as feedsjson:
    entry = {'name': args.name, 'url': args.url}
    feeds.append(entry)
    json.dump(feeds, feedsjson)

Note that this will be slow to execute because you will rewrite the full contents of the file every time you call add. If you are calling it in a loop, consider adding all the feeds to a list in advance, then writing the list out in one go.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 18
    Thanks. `feeds` is not defined in your code example. Should it be a json.dump of the original file? That's what I've been trying, and it doesn't seem to work. – Schiphol Oct 21 '12 at 10:07
  • Sorry. It's meant to replace just the write block in your original `add`. – nneonneo Oct 21 '12 at 16:17
  • When you initial the file with `with open(DATA_FILENAME, mode='r', encoding='utf-8') as f: json.dump([], f)`, you'll want to use `with open(DATA_FILENAME, mode='w', encoding='utf-8') as f: json.dump([], f)` instead to open the file for writing, instead of reading (if the file doesn't exist, it can't be read). – Blairg23 Nov 05 '15 at 12:56
  • Whoops, fixed. Thanks. – nneonneo Nov 05 '15 at 16:16
  • not a big issue for smaller files but does this not load the entire json into memory then re-append to it, so you're doing lots read/write/overwrite operations? – Umar.H Feb 02 '21 at 15:04
  • 1
    @Manakin: Yes. If you are appending often, you may want to use a different format (see accepted answer), or put each JSON object on a separate line (and read one object per line - instead of the whole file as a single object). – nneonneo Feb 02 '21 at 19:40
21

Append entry to the file contents if file exists, otherwise append the entry to an empty list and write in in the file:

a = []
if not os.path.isfile(fname):
    a.append(entry)
    with open(fname, mode='w') as f:
        f.write(json.dumps(a, indent=2))
else:
    with open(fname) as feedsjson:
        feeds = json.load(feedsjson)

    feeds.append(entry)
    with open(fname, mode='w') as f:
        f.write(json.dumps(feeds, indent=2))
voaneves
  • 62
  • 8
NargesooTv
  • 837
  • 9
  • 15
  • 3
    feeds is a dictionary. I get the following error when I try your code: AttributeError: 'dict' object has no attribute 'append' However, If I change append with update, it works – Oeyvind Jul 11 '19 at 11:21
  • 1
    Also, this can result into duplicate entries as well. – Ciasto piekarz Jan 10 '20 at 11:31
18

Using a instead of w should let you update the file instead of creating a new one/overwriting everything in the existing file.

See this answer for a difference in the modes.

Community
  • 1
  • 1
Susan Wright
  • 406
  • 1
  • 4
  • 13
  • 2
    BEAUTIFUL! just needed 'a' instead of 'w'. nothing crazy – Paul Oct 25 '17 at 19:22
  • 4
    This does not work for me. It just concatenates two different lists, invalidating the json. It appears like this: `[data1, data2, ..., dataN][dataN+1, dataN+2, ...]` – Eduardo Pignatelli Apr 04 '18 at 09:37
  • 9
    This just appends onto the **file**, not the array inside the file. – Milan Velebit Apr 30 '18 at 11:24
  • 1
    Assuming your writes and reads are periodic, you could use this method and then in a separate thread or process have a little function that flattens the file back into a JSON readable format. Not as fast as pure appending but it should at least make your writes fast. – DerekG Nov 13 '19 at 21:41
  • @EduardoPignatelli You can just read it back line by line and then merge it later: `for line in f.readlines(): data = json.loads(line)` – SurpriseDog Jun 18 '22 at 00:54
  • @SurpriseDog That is not a valid json format. – Eduardo Pignatelli Jun 21 '22 at 15:47
  • True, but it depends what your goals are. Do you need a valid json file at all times? or do you just want an easy way to append data. – SurpriseDog Jun 21 '22 at 15:56
15

One possible solution is do the concatenation manually, here is some useful code:

import json
def append_to_json(_dict,path): 
    with open(path, 'ab+') as f:
        f.seek(0,2)                                #Go to the end of file    
        if f.tell() == 0 :                         #Check if file is empty
            f.write(json.dumps([_dict]).encode())  #If empty, write an array
        else :
            f.seek(-1,2)           
            f.truncate()                           #Remove the last character, open the array
            f.write(' , '.encode())                #Write the separator
            f.write(json.dumps(_dict).encode())    #Dump the dictionary
            f.write(']'.encode())                  #Close the array

You should be careful when editing the file outside the script not add any spacing at the end.

Hamza Rashid
  • 1,329
  • 15
  • 22
SEDaradji
  • 1,348
  • 15
  • 18
  • 1
    Sadly it will not work anymore in Python 3.2+ if not in binary mode since you need to tell the encoding. The following stackoverflow question gives some hint : https://stackoverflow.com/questions/18857352/python-remove-very-last-character-in-file – Brown nightingale Jan 23 '18 at 00:03
  • This code is probably what author looking for, however it have a problem. For empty file it add double quotes, for appending it not removing them so it looks nested, the comma have extra space which json don't have in default dump behavior, strings are also extra-encoded which may be just byte string form the start. So I edit code to work as intended. – XCanG Apr 30 '19 at 04:13
6

this, work for me :

with open('file.json', 'a') as outfile:
    outfile.write(json.dumps(data))
    outfile.write(",")
    outfile.close()
hassanzadeh.sd
  • 3,091
  • 1
  • 17
  • 26
  • 1
    If you do like that then the end `]` will not be at the right place, also if you end a `.json` with `,` it won't be a valid `JSON anymore.... – Korte Alma Nov 11 '22 at 19:15
5

I have some code which is similar, but does not rewrite the entire contents each time. This is meant to run periodically and append a JSON entry at the end of an array.

If the file doesn't exist yet, it creates it and dumps the JSON into an array. If the file has already been created, it goes to the end, replaces the ] with a , drops the new JSON object in, and then closes it up again with another ]

# Append JSON object to output file JSON array
fname = "somefile.txt"
if os.path.isfile(fname):
    # File exists
    with open(fname, 'a+') as outfile:
        outfile.seek(-1, os.SEEK_END)
        outfile.truncate()
        outfile.write(',')
        json.dump(data_dict, outfile)
        outfile.write(']')
else: 
    # Create file
    with open(fname, 'w') as outfile:
        array = []
        array.append(data_dict)
        json.dump(array, outfile)
Matt
  • 281
  • 4
  • 9
4

You aren't ever writing anything to do with the data you read in. Do you want to be adding the data structure in feeds to the new one you're creating?

Or perhaps you want to open the file in append mode open(filename, 'a') and then add your string, by writing the string produced by json.dumps instead of using json.dump - but nneonneo points out that this would be invalid json.

Thomas
  • 6,515
  • 1
  • 31
  • 47
  • 2
    It's a good try, but it's going to write invalid JSON to the file (e.g `{"a":"b"}{"c":"d"}`). – nneonneo Oct 21 '12 at 02:41
  • Yes, I meant to add the data structure in feeds to the one I am creating. Let me edit the question to make that clear. – Schiphol Oct 21 '12 at 02:47
4
import jsonlines

object1 = {
               "name": "name1",
               "url": "url1"
          }

object2 = {
               "name": "name2",
               "url": "url2"
          }   


# filename.jsonl is the name of the file
with jsonlines.open("filename.jsonl", "a") as writer:   # for writing
    writer.write(object1)
    writer.write(object2)

with jsonlines.open('filename.jsonl') as reader:      # for reading
    for obj in reader:
        print(obj)             

visit for more info https://jsonlines.readthedocs.io/en/latest/

2

You can simply import the data from the source file, read it, and save what you want to append to a variable. Then open the destination file, assign the list data inside to a new variable (presumably this will all be valid JSON), then use the 'append' function on this list variable and append the first variable to it. Viola, you have appended to the JSON list. Now just overwrite your destination file with the newly appended list (as JSON).

The 'a' mode in your 'open' function will not work here because it will just tack everything on to the end of the file, which will make it non-valid JSON format.

Toakley
  • 182
  • 3
  • 13
0

let's say you have the following dicts

d1 = {'a': 'apple'}
d2 = {'b': 'banana'}
d3 = {'c': 'carrot'}

you can turn this into a combined json like this:

master_json = str(json.dumps(d1))[:-1]+', '+str(json.dumps(d2))[1:-1]+', '+str(json.dumps(d3))[1:]

therefore, code to append to a json file will look like below:

        dict_list = [d1, d2, d3]
        for i, d in enumerate(d_list):
            if i == 0:
                #first dict
                start = str(json.dumps(d))[:-1]
                with open(str_file_name, mode='w') as f:
                    f.write(start)
            else:
                with open(str_file_name, mode='a') as f:
                    if i != (len(dict_list) - 1):
                        #middle dicts
                        mid = ','+str(json.dumps(d))[1:-1]
                        f.write(mid)
                    else:
                        #last dict
                        end = ','+str(json.dumps(d))[1:]
                        f.write(end)
0

Use json.load, json.update, json.dump like so for instance:

    with open('data.json', mode='r') as file:
        data = json.load(file)

        new_record = {key: {inner_key:content}} # Whatever your structure is
        data.update(new_record)

    with open('data.json', mode='w') as file:
        data.dump(data, file, indent=4)
Jörg
  • 15
  • 1
  • 7