├── .gitignore ├── LICENSE ├── README.md ├── example ├── generated │ ├── ArrayItem.cpp │ ├── ArrayItem.h │ ├── Subobj.cpp │ ├── Subobj.h │ ├── User.cpp │ └── User.h └── user.json └── json2cpp.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 KCris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json2cpp 2 | 3 | ## python script that generates c++ mapping classes for a given json 4 | 5 | The typical c++ json parser library, loads json values into a map(string->variant), which discards all static checking that might be done by a compiler and that is error prone at runtime. 6 | 7 | If a well known, fixed json format is used (taken from a json sample), direct mapping to c++ classes/members (preserving types) will allow typesafe client code. 8 | 9 | ## About 10 | * generates c++ (11) classes with json load/save methods 11 | * each json object in our sample becomes a c++ class, each json property of such an object becomes a c++ class member 12 | * generated code still relies on a third party json parser library, currently [cpprest](https://github.com/Microsoft/cpprestsdk) 13 | * json arrays must be homogeneous (all elements must have same 'type') 14 | * the original script is available [here](https://gist.github.com/soharu/5083914) (no subobjects supported) 15 | -------------------------------------------------------------------------------- /example/generated/ArrayItem.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ArrrayItem.cpp 3 | // 4 | // -- generated class for jsoncpp 5 | // 6 | #include "ArrrayItem.h" 7 | 8 | //equals operator 9 | bool ArrrayItem::operator==(const ArrrayItem & other) const 10 | { 11 | return 12 | std::tie(m_prop) 13 | == 14 | std::tie(other.m_prop); 15 | } 16 | 17 | //lessThan operator 18 | bool ArrrayItem::operator<(const ArrrayItem & other) const 19 | { 20 | return 21 | std::tie(m_prop) 22 | < 23 | std::tie(other.m_prop); 24 | } 25 | 26 | // parse 27 | bool ArrrayItem::load(const web::json::value & jsonObject) 28 | { 29 | try 30 | { 31 | m_prop = jsonObject.at(L"prop").as_string(); 32 | } 33 | catch(web::json::json_exception &) { return false; } 34 | 35 | return true; 36 | } 37 | 38 | // serialize 39 | web::json::value ArrrayItem::save() const 40 | { 41 | web::json::value jsonObject; 42 | 43 | jsonObject[L"prop"] = web::json::value::string(m_prop); 44 | 45 | return jsonObject; 46 | } 47 | -------------------------------------------------------------------------------- /example/generated/ArrayItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // ArrrayItem.h 3 | // 4 | // -- generated file, do NOT edit 5 | // 6 | #pragma once 7 | 8 | #include //cpprest library used for json serialization 9 | 10 | 11 | 12 | struct ArrrayItem 13 | { 14 | //constructors 15 | ArrrayItem() = default; 16 | ArrrayItem(const web::json::value & jsonObject) : ArrrayItem() { load(jsonObject); } 17 | 18 | //operators (see std::rel_ops) 19 | bool operator==(const ArrrayItem & other) const; 20 | bool operator<(const ArrrayItem & other) const; 21 | 22 | //json parsing and serializing 23 | bool load(const web::json::value & jsonObject); 24 | web::json::value save() const; 25 | 26 | //member data 27 | std::wstring m_prop; 28 | }; 29 | -------------------------------------------------------------------------------- /example/generated/Subobj.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Subobj.cpp 3 | // 4 | // -- generated class for jsoncpp 5 | // 6 | #include "Subobj.h" 7 | 8 | //equals operator 9 | bool Subobj::operator==(const Subobj & other) const 10 | { 11 | return 12 | std::tie(m_prop) 13 | == 14 | std::tie(other.m_prop); 15 | } 16 | 17 | //lessThan operator 18 | bool Subobj::operator<(const Subobj & other) const 19 | { 20 | return 21 | std::tie(m_prop) 22 | < 23 | std::tie(other.m_prop); 24 | } 25 | 26 | // parse 27 | bool Subobj::load(const web::json::value & jsonObject) 28 | { 29 | try 30 | { 31 | m_prop = jsonObject.at(L"prop").as_integer(); 32 | } 33 | catch(web::json::json_exception &) { return false; } 34 | 35 | return true; 36 | } 37 | 38 | // serialize 39 | web::json::value Subobj::save() const 40 | { 41 | web::json::value jsonObject; 42 | 43 | jsonObject[L"prop"] = web::json::value::number(m_prop); 44 | 45 | return jsonObject; 46 | } 47 | -------------------------------------------------------------------------------- /example/generated/Subobj.h: -------------------------------------------------------------------------------- 1 | // 2 | // Subobj.h 3 | // 4 | // -- generated file, do NOT edit 5 | // 6 | #pragma once 7 | 8 | #include //cpprest library used for json serialization 9 | 10 | 11 | 12 | struct Subobj 13 | { 14 | //constructors 15 | Subobj() = default; 16 | Subobj(const web::json::value & jsonObject) : Subobj() { load(jsonObject); } 17 | 18 | //operators (see std::rel_ops) 19 | bool operator==(const Subobj & other) const; 20 | bool operator<(const Subobj & other) const; 21 | 22 | //json parsing and serializing 23 | bool load(const web::json::value & jsonObject); 24 | web::json::value save() const; 25 | 26 | //member data 27 | int m_prop; 28 | }; 29 | -------------------------------------------------------------------------------- /example/generated/User.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // User.cpp 3 | // 4 | // -- generated class for jsoncpp 5 | // 6 | #include "User.h" 7 | 8 | //equals operator 9 | bool User::operator==(const User & other) const 10 | { 11 | return 12 | std::tie(m_verified, m_subobj, m_weight, m_userid, m_arrray, m_items, m_games, m_username) 13 | == 14 | std::tie(other.m_verified, other.m_subobj, other.m_weight, other.m_userid, other.m_arrray, other.m_items, other.m_games, other.m_username); 15 | } 16 | 17 | //lessThan operator 18 | bool User::operator<(const User & other) const 19 | { 20 | return 21 | std::tie(m_verified, m_subobj, m_weight, m_userid, m_arrray, m_items, m_games, m_username) 22 | < 23 | std::tie(other.m_verified, other.m_subobj, other.m_weight, other.m_userid, other.m_arrray, other.m_items, other.m_games, other.m_username); 24 | } 25 | 26 | // parse 27 | bool User::load(const web::json::value & jsonObject) 28 | { 29 | try 30 | { 31 | m_verified = jsonObject.at(L"verified").as_bool(); 32 | m_subobj.load(jsonObject.at(L"subobj")); //json object 33 | m_weight = jsonObject.at(L"weight").as_double(); 34 | m_userid = jsonObject.at(L"userid").as_integer(); 35 | 36 | for (const web::json::value & item : jsonObject.at(L"arrray").as_array()) 37 | m_arrray.push_back(ArrrayItem(item)); //json objects 38 | 39 | for (const web::json::value & item : jsonObject.at(L"items").as_array()) 40 | m_items.push_back(item.as_integer()); 41 | 42 | for (const web::json::value & item : jsonObject.at(L"games").as_array()) 43 | m_games.push_back(item.as_string()); 44 | m_username = jsonObject.at(L"username").as_string(); 45 | } 46 | catch(web::json::json_exception &) { return false; } 47 | 48 | return true; 49 | } 50 | 51 | // serialize 52 | web::json::value User::save() const 53 | { 54 | web::json::value jsonObject; 55 | 56 | jsonObject[L"verified"] = web::json::value::boolean(m_verified); 57 | jsonObject[L"subobj"] = m_subobj.save(); //json object 58 | jsonObject[L"weight"] = web::json::value::number(m_weight); 59 | jsonObject[L"userid"] = web::json::value::number(m_userid); 60 | 61 | std::vector t_arrray; 62 | for (const auto & item : m_arrray) 63 | t_arrray.push_back(item.save()); //json objects 64 | jsonObject[L"arrray"] = web::json::value::array(t_arrray); 65 | 66 | std::vector t_items; 67 | for (const auto & item : m_items) 68 | t_items.push_back(web::json::value::number(item)); 69 | jsonObject[L"items"] = web::json::value::array(t_items); 70 | 71 | std::vector t_games; 72 | for (const auto & item : m_games) 73 | t_games.push_back(web::json::value::string(item)); 74 | jsonObject[L"games"] = web::json::value::array(t_games); 75 | jsonObject[L"username"] = web::json::value::string(m_username); 76 | 77 | return jsonObject; 78 | } 79 | -------------------------------------------------------------------------------- /example/generated/User.h: -------------------------------------------------------------------------------- 1 | // 2 | // User.h 3 | // 4 | // -- generated file, do NOT edit 5 | // 6 | #pragma once 7 | 8 | #include //cpprest library used for json serialization 9 | 10 | #include "subobj.h" //generated 11 | #include "ArrrayItem.h" //generated 12 | 13 | 14 | struct User 15 | { 16 | //constructors 17 | User() = default; 18 | User(const web::json::value & jsonObject) : User() { load(jsonObject); } 19 | 20 | //operators (see std::rel_ops) 21 | bool operator==(const User & other) const; 22 | bool operator<(const User & other) const; 23 | 24 | //json parsing and serializing 25 | bool load(const web::json::value & jsonObject); 26 | web::json::value save() const; 27 | 28 | //member data 29 | bool m_verified; 30 | Subobj m_subobj; 31 | double m_weight; 32 | int m_userid; 33 | std::vector m_arrray; 34 | std::vector m_items; 35 | std::vector m_games; 36 | std::wstring m_username; 37 | }; 38 | -------------------------------------------------------------------------------- /example/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "userid": 1000, 3 | "username": "banana", 4 | "verified": true, 5 | "weight": 123.45, 6 | "items": [ 1, 2, 3, 4 ], 7 | "games":["tiny farm", "heroes war", "derby days"], 8 | "subobj": 9 | { 10 | "prop": 9191 11 | }, 12 | "arrray": [{"prop":"val1"}, {"prop":"val2"}] 13 | } 14 | -------------------------------------------------------------------------------- /json2cpp.py: -------------------------------------------------------------------------------- 1 | #!/user/bin/env python 2 | 3 | import json 4 | import os 5 | import sys 6 | from types import * 7 | #from collections import OrderedDict 8 | 9 | def cpp_identifier(name): 10 | identifier = name 11 | if (identifier[0].isdigit()): 12 | identifier = "_" + identifier 13 | return identifier.replace(".", "_") 14 | 15 | def cpp_typename(name): 16 | return cpp_identifier(name) 17 | 18 | def cpp_filename(name): 19 | return cpp_identifier(name) 20 | 21 | def cpp_type(value): 22 | if isinstance(value, bool): 23 | return 'bool' 24 | elif isinstance(value, int): 25 | return 'int' 26 | elif isinstance(value, float): 27 | return 'double' 28 | elif isinstance(value, str): 29 | return 'std::wstring' 30 | elif isinstance(value, list): 31 | return 'std::vector' 32 | elif isinstance(value, dict): 33 | return 'class' #'std::map' #TODO - anonymous class (in array?) ?? 34 | else: #dict? 35 | #print("unsupported type {}".format(type(value))) 36 | pass #return None 37 | 38 | def generate_variable_info(data): 39 | includes = [] 40 | varinfo = [] 41 | for k, v in list(data.items()): 42 | if isinstance(v, list): #json array 43 | #if not 'vector' in includes: 44 | # includes.append('#include ') 45 | if isinstance(v[0], dict): 46 | includes.append('#include "{}Item.h" //generated'.format(cpp_filename(k.title()))) 47 | varinfo.append((cpp_type(v), cpp_type(v[0]), k, v)) 48 | else: 49 | varinfo.append((cpp_type(v), cpp_type(v[0]), k)) 50 | elif isinstance(v, dict): #json object 51 | if not k in includes: 52 | includes.append('#include "{}.h" //generated'.format(cpp_filename(k))) 53 | varinfo.append(('class',k, data[k])) 54 | else: #simple type 55 | typename = cpp_type(v) 56 | varinfo.append((typename, k)) 57 | #if 'string' in typename and not 'string' in includes: 58 | # includes.append('#include ') 59 | return includes, varinfo 60 | 61 | # c++ parser library specific stuff 62 | type_json_cpp = "web::json::value" 63 | type_methods_load = {'int':'as_integer', 'bool':'as_bool', 'double':'as_double', 'std::wstring':'as_string'} 64 | type_methods_save = {'int':'web::json::value::number', 'bool':'web::json::value::boolean', 'double':'web::json::value::number', 'std::wstring':'web::json::value::string'} 65 | 66 | def assign_statement_load(t, v): 67 | return 'm_{} = jsonObject.at(L"{}").{}();'.format(cpp_identifier(v), v, type_methods_load[t]) 68 | 69 | def assign_statement_save(t, v): 70 | return 'jsonObject[L"{}"] = {}(m_{});'.format(v, type_methods_save[t], cpp_identifier(v)) 71 | 72 | def array2vector_statements(elemt, var): 73 | stats = [] 74 | stats.append('\n\t\tfor (const {} & item : jsonObject.at(L"{}").as_array())'.format(type_json_cpp, var)) 75 | if elemt == 'class': 76 | stats.append('\tm_{}.push_back({}(item)); //json objects'.format(cpp_identifier(var), "{}Item".format(cpp_identifier(var.title())))) #array of objects 77 | else: 78 | stats.append('\tm_{}.push_back(item.{}());'.format(cpp_identifier(var), type_methods_load[elemt])) #simple array 79 | return stats 80 | 81 | def vector2array_statements(elemt, var): 82 | stats = [] 83 | temp = 't_'+ cpp_identifier(var) 84 | stats.append('\n\tstd::vector<{}> {};'.format(type_json_cpp, temp)) 85 | stats.append('for (const auto & item : m_{})'.format(cpp_identifier(var))) 86 | if elemt == 'class': #array of objects 87 | stats.append('\t{}.push_back(item.save()); //json objects'.format(temp)) 88 | else: #simple array 89 | stats.append('\t{}.push_back({}(item));'.format(temp, type_methods_save[elemt])) 90 | stats.append('jsonObject[L"{}"] = web::json::value::array({});'.format(var, temp)) 91 | return stats 92 | 93 | def membersList(varinfo, obj = None): 94 | members = [] 95 | for info in varinfo: 96 | memberName = 'm_' if obj is None else '{}.m_'.format(obj) 97 | memberName += cpp_identifier(info[2] if len(info) >= 3 and info[0] != "class" else info[1]) 98 | members.append(memberName) 99 | return ", ".join(members) 100 | 101 | # generate output .h 102 | def generate_header(classname, includes, varinfo, dirname): 103 | f = open(dirname + '/' + classname + '.h', 'wt') 104 | f.write('//\n// {}.h\n'.format(classname)) 105 | f.write('//\n// -- generated file, do NOT edit\n//\n') 106 | f.write('#pragma once\n\n') 107 | f.write('#include //cpprest library used for json serialization\n\n') 108 | for i in range(len(includes)): 109 | f.write("{}\n".format(includes[i])) 110 | #class definition 111 | f.write('\n\nstruct {}\n'.format(classname)) 112 | f.write('{\n') 113 | #constructors 114 | f.write('\t//constructors\n') 115 | f.write('\t{}() = default;\n'.format(classname)) #explicitly defaulted ctor (force compiler generated default-ctor since the user ctor below otherwise inhibits it) 116 | f.write('\t{}(const {} & jsonObject) : {}() {{ load(jsonObject); }}\n\n'.format(classname, type_json_cpp, classname)) #user ctor 117 | #operators 118 | f.write('\t//operators (see std::rel_ops)\n') 119 | f.write('\tbool operator==(const {} & other) const;\n'.format(classname)) 120 | #f.write('\tbool operator!=(const {} & other) const {{ return !(*this == other); }}\n'.format(classname)) #see std::rel_ops instead 121 | f.write('\tbool operator<(const {} & other) const;\n\n'.format(classname)) 122 | #load/save methods 123 | f.write('\t//json parsing and serializing\n') 124 | f.write('\tbool load(const {} & jsonObject);\n'.format(type_json_cpp)) 125 | f.write('\t{} save() const;\n\n'.format(type_json_cpp)) 126 | #data members 127 | f.write('\t//member data\n') 128 | for info in varinfo: 129 | if len(info) >= 3: 130 | if info[0] == "class": #json object 131 | f.write('\t{} m_{};\n'.format(cpp_typename(info[1].title()), cpp_identifier(info[1]))) 132 | else: #json array 133 | itemType = "{}Item".format(info[2].title()) if info[1] == "class" else info[1] 134 | f.write('\t{}<{}> m_{};\n'.format(info[0], cpp_typename(itemType), cpp_identifier(info[2]))) 135 | elif len(info) == 2: #simple type 136 | f.write('\t{} m_{};\n'.format(cpp_typename(info[0]), cpp_identifier(info[1]))) 137 | f.write('};\n') 138 | 139 | # generate output .cpp 140 | def generate_source(classname, varinfo, dirname): 141 | f = open(dirname + '/' + classname + '.cpp', 'wt') 142 | f.write('//\n// {}.cpp\n'.format(classname)) 143 | f.write('//\n// -- generated class for jsoncpp\n//\n') 144 | f.write('#include \"{}.h\"\n\n'.format(classname)) 145 | #operators 146 | f.write('//equals operator\n') 147 | f.write('bool {}::operator==(const {} & other) const\n'.format(classname, classname)) 148 | f.write('{\n') 149 | f.write('\treturn\n\t\tstd::tie({})\n\t\t==\n\t\tstd::tie({});\n'.format(membersList(varinfo), membersList(varinfo, "other"))) 150 | f.write('}\n\n') 151 | f.write('//lessThan operator\n') 152 | f.write('bool {}::operator<(const {} & other) const\n'.format(classname, classname)) 153 | f.write('{\n') 154 | f.write('\treturn\n\t\tstd::tie({})\n\t\t<\n\t\tstd::tie({});\n'.format(membersList(varinfo), membersList(varinfo, "other"))) 155 | f.write('}\n\n') 156 | #load method: 157 | f.write('// parse\n') 158 | f.write('bool {}::load(const {} & jsonObject)\n'.format(classname, type_json_cpp)) 159 | f.write('{\n') 160 | f.write('\ttry\n\t{\n') #try 161 | for info in varinfo: 162 | if len(info) >= 3: 163 | if (info[0] == "class"): #json object 164 | f.write('\t\tm_{}.load(jsonObject.at(L"{}")); //json object\n'.format(cpp_identifier(info[1]), info[1])) 165 | generate(info[1].title(), info[2], dirname) #recursively generate related classes 166 | elif 'vector' in info[0]: #json array 167 | for line in array2vector_statements(info[1], info[2]): 168 | f.write('\t\t'+line+'\n') 169 | if info[1] == "class": 170 | generate("{}Item".format(info[2].title()), info[3][0], dirname) #recursively generate related classes 171 | elif len(info) == 2: #simple type 172 | if not info[0] is None: 173 | f.write('\t\t'+assign_statement_load(info[0], info[1])+'\n') 174 | f.write('\t}\n\tcatch(web::json::json_exception &) { return false; }\n') #catch 175 | f.write('\n\treturn true;\n}\n\n') 176 | #save method 177 | f.write('// serialize\n') 178 | f.write('{} {}::save() const\n'.format(type_json_cpp, classname)) 179 | f.write('{\n') 180 | f.write('\t{} jsonObject;\n\n'.format(type_json_cpp)) 181 | for info in varinfo: 182 | if len(info) >= 3: 183 | if (info[0] == "class"): #json object 184 | f.write('\tjsonObject[L"{}"] = m_{}.save(); //json object\n'.format(info[1], cpp_identifier(info[1]))) 185 | elif 'vector' in info[0]: #json array 186 | for line in vector2array_statements(info[1], info[2]): 187 | f.write('\t'+line+'\n') 188 | elif len(info) == 2: #simple type 189 | if not info[0] is None: 190 | f.write('\t'+assign_statement_save(info[0], info[1])+'\n') 191 | f.write('\n\treturn jsonObject;\n') 192 | f.write('}\n') 193 | 194 | # generate output files 195 | def generate(classname, data, dirname): 196 | includes, varinfo = generate_variable_info(data) 197 | varinfo.sort() 198 | try: 199 | os.stat(dirname) 200 | except: 201 | os.mkdir(dirname) 202 | classId = cpp_identifier(classname) 203 | generate_header(classId, includes, varinfo, dirname) 204 | generate_source(classId, varinfo, dirname) 205 | print("Generated class: {}".format(classId)) 206 | 207 | #entry point 208 | if __name__ == '__main__': 209 | if len(sys.argv) != 2: 210 | print ('Usage: {} '.format(sys.argv[0])) 211 | sys.exit(1) 212 | filename = sys.argv[1] 213 | classname = cpp_identifier(filename.split('.')[0]) 214 | try: 215 | with open(filename) as f: 216 | content = f.read() 217 | except IOError: 218 | print ("Can't open/read file".format(filename)) 219 | sys.exit(2) 220 | data = json.loads(content) #data = json.loads(content, object_pairs_hook=OrderedDict) 221 | generate(classname.title(), data, "out." + classname) 222 | sys.exit(0) 223 | 224 | # 225 | # based on https://gist.github.com/soharu/5083914 226 | # 227 | # Generate c++(11) mapping classes for the object(s) inside a given json file. 228 | # Each data member of a generated class is mapped onto an existing json property. 229 | # Note: the json is actually loaded/saved using an external library (cpprest). 230 | # 231 | # Warning: json arrays must be homogeneous 232 | # 233 | --------------------------------------------------------------------------------