Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

How do I read JSON into a Python Class?

no_longer_in_sudoers_file
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
August 13, 2016

I have a JSON configuration file that looks like this:

{
    "container":{
        "image":"reporting_engine",
        "name":"reporting_engine",
        "version":"0.01",
        "network":{
            "host":{
                "ip":"127.0.1.1",
                "port":"5000"
            },
            "container":{
                "ip":"127.0.1.1",
                "port":"5000"
            }
        },
        "storage":{
            "host":"/home/scaldwell/rpe/data",
            "container":"/opt/app/data"
        }
    }
}

I want to read this JSON configuration file into a Python object so I can address properties in an intuitive way programmatically.

I realize I could do something like this: 

import json
with open(config_file,'r') as f:
    config=json.loads(f.read())

and then use my configuration parameters like this:

print config["network"]["host"]["ip"]

 

While that is acceptable Python, it's about as elegant as a Trump speech and possibly just as likely to have errors since there all of those open and closing brackets and quotes.

 

I would rather a solution that allows me to do the same print like this:

print config.network.host.ip

...like a civilized code monkey. wink

1 answer

1 accepted

1 vote
Answer accepted
no_longer_in_sudoers_file
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
August 13, 2016

This code works on my current personal project. It allows me to use config.py to read JSON config files from a shell script that launches and manages my docker containers while also using that same config.py from other python scripts (for the containerized application) to read config properties.

Config files are like Highlander, IMHO. In the end there should be only one!

 

Here is my solution (config.py):

#!/usr/bin/env python
import os
import sys
import time
import json
import inspect
from argparse import ArgumentParser

def parse_args():
    parser=ArgumentParser(
        description="read/write json config file."
    )
    parser.add_argument(
        "--debug",
        action="store_true",
        default=False,
        help="Determines whether verbose log messages should be printed."
    )
    parser.add_argument(
        "--read",
        action="store_true",
        default=False,
        help="Read the file (--config) and return a key (requires --key)."
    )
    parser.add_argument(
        "--write",
        action="store_true",
        default=False,
        help="Write a key (--key) value (--value) pair to --config"
    )
    parser.add_argument(
        "--key",
        dest="key",
        type=str,
        help="Key name to be read/written"
    )
    parser.add_argument(
        "--value",
        dest="value",
        type=str,
        help="Data value to be written."
    )
    parser.add_argument(
        "--config",
        dest="config",
        default="app.json",
        type=str,
        help="The configuration file to be read/written."
    )
    a=parser.parse_args()
    if a.read or a.write:
        if a.read and a.write:
            print "You cannot use both --read and --write concurrently"
            sys.exit(1)
        else:
            return a
    else:   
        print "You must specify either --read or --write"
        sys.exit(1)
class Config:
   
    def __init__(self,oName,is_file=False,i=0,debug_mode=False):
        self.debug_mode=debug_mode
        self.i=i
        self.type=None
        if is_file:
            self.oName="root"
            self.type="root"
        else:
            self.oName=oName
            self.type="child"
        self.debug("init {}[{}] starting".format(self.oName,self.type))
        if is_file:
            j=None
            try:
                with open(oName,'r') as f:
                    j=json.loads(f.read())
                self.debug(
                    "----Raw Input----\n\n{}\n".format(
                        json.dumps(j,indent=4)
                    )
                )
                self.deserialize(j,i+1)
            except Exception as e:
                self.debug("Could not load config file.  Error:{}".format(e))
                sys.exit(1)
            
        self.debug("init {}[{}] complete".format(self.oName,self.type))
    def deserialize(self,j,i=0):
        self.i=i
        try:
            self.debug(" ")
            self.debug("---deserializer loop started")
            self.debug("+>json:{}".format(j))
            for k,v in j.viewitems():
                self.debug("calling walk() with '{}'".format(k))
                self.walk(k,v,i+1)
                self.debug("returned from walk() '{}'".format(k))
            self.debug("---deserializer loop complete.")
        except Exception as e:
            self.debug("Could not deserialize config. Error:".format(e))
            sys.exit(1)
        self.i-=1
    def walk(self,key_name,key_value,i=1):
        self.i=i
        self.debug("walking the config:{}".format(i))
        try:
            o=None
            if type(key_value) is dict:
                o=Config(oName=key_name,i=i)
                o.deserialize(key_value,i+1)
                self.debug("walk(): recovered from recursive call.")
                assert key_name != "oName", "oName is reserved for parser"
                setattr(self,key_name,o)
            else:
                self.debug("Set value:{},{}".format(key_name,key_value))
                assert key_name != "oName", "oName is reserved for parser"
                setattr(self,key_name,key_value)
        except Exception as e:
            self.debug("config.walk() Error:{}".format(e))
            sys.exit(1)
        self.i-=1
    def dump_config(self,i=0,data_only=False):
        old_debug_mode=self.debug_mode
        self.debug_mode=True
        self.i=i
        if i == 0:
            c="*"
        else:
            c="-"
        self.debug("{0}DUMP CONFIG START ({1}:{2}){0}".format(c*3,self.type,self.oName))
        for k in dir(self):
            if data_only and \
               not k.startswith("_") and \
               not callable(getattr(self,k)):
                self.debug("{0:15}:{1}".format(k,getattr(self,k)))
            if isinstance(getattr(self,k),Config):
                self.i+=1
                o=getattr(self,k)
                o.dump_config(self.i+1,data_only)
                self.i-=1
        self.debug("{0}DUMP CONFIG ENDS ({1}:{2}){0}".format(c*3,self.type,self.oName))
        self.debug_mode=old_debug_mode
    def debug(self,m):
        try:
            if self.debug_mode:
                print "{0:15}|{1:<2}| {2} [{3}]:{4}".format(
                    int(time.time()*10000000000),
                    hex(self.i),
                    " . "*self.i,
                    self.oName,
                    m
                )
        except Exception as e:
            print "Could not print message: Error:{}".format(e)
            sys.exit(1)
    def getChildAttribute(self,index,object,path):
        self.i+=1
        assert isinstance(self,Config),"self must be <instance>"
        assert type(index) is int, "index must be <int>"
        assert isinstance(object,Config),"object must be <instance>"
        assert type(path) is list, "path must be <list>"
        for i in path: 
            assert type(i) in [str,unicode], "path elements must be <str>"
        assert index >= 0, "index must be > 0"
        assert len(path) > 0, "len(path <list>) must be > 0"
        self.debug("position[{}]:{}".format(index,path[index]))
        o=getattr(object,path[index])
        if (index < len(path)) and isinstance(o,Config):
            return self.getChildAttribute(index+1,o,path)
        else:
            return o
        self.i-=1
    def read_value(self,k):
        if self.debug_mode:self.dump_config(data_only=True)
        self.i=0
        try:
            path=k.split('.')
            self.debug("path: {}".format(path))
            o=getattr(self,path[0])
            self.debug("o:{}:{}".format(o,o.oName))
            return 0, self.getChildAttribute(
                                    index=1,
                                    object=o,
                                    path=path
            )
        except Exception as e:
            return 1,e
    def write_value(self,k,v):
        try:
            assert k is not None, "Expected a key string (--key)"
            assert v is not None, "Expected a value string (--value)"
            assert type(k) is str, "--key must specify a string."
            assert type(v) is str, "--value must specify a string."
        except Exception as e:
            print "Error: {}".format(e) 
            sys.exit(1)
        try:
            print "write_value() not implemented yet."
        except Exception as e:
            print "write_value() Error: {}".format(e)
            sys.exit(1)
def main():
    args=parse_args()
    config=Config(args.config,is_file=True,debug_mode=args.debug)
    if args.read:
        e,v=config.read_value(args.key)
        if args.debug:
            print "Key:'{}', Value:'{}', ExitCode:{}".format(args.key,v,e)
        else:
            print v
        sys.exit(e)
    elif args.write:
        sys.exit(config.write_value(args.key,args.value))
    else:
        print "Uncaught input parameter error.  Expected --read or --write"
        sys.exit(1)
if __name__ == "__main__":
    main()

This allows me to have a config file like this:

{
    "container":{
        "image":"reporting_engine",
        "name":"reporting_engine",
        "ip":"127.0.1.1",
        "port":"5000",
        "volume":"/storage"
    },
    "app":{
    }
}

..and to then use the utility to fetch JSON config elements like this: 

python common/config --read --config reporting_engine/app.json --key container.image

Which will write the following to stdout: 

reporting_engine

As the JSON config changes, the code doesn't.  That's my favorite kind of software (the flexible kind). wink

Steven F Behnke
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
August 13, 2016

image2016-8-13 11:57:48.png

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events