├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── examples ├── example_asyncapi.py ├── example_json.py ├── example_markdown.py ├── example_python.py └── example_usage.py ├── jsonschemacodegen ├── __init__.py ├── _version.py ├── cpp.py ├── json_example.py ├── python.py ├── resolver.py ├── schemawrappers.py └── templates │ ├── cpp │ ├── __init__.py │ ├── allof.cpp.jinja2 │ ├── allof.hpp.jinja2 │ ├── anyof.cpp.jinja2 │ ├── anyof.hpp.jinja2 │ ├── array.cpp.jinja2 │ ├── array.hpp.jinja2 │ ├── bool.cpp.jinja2 │ ├── bool.hpp.jinja2 │ ├── constraints.jinja2 │ ├── datetime.cpp.jinja2 │ ├── datetime.hpp.jinja2 │ ├── exceptions.hpp.jinja2 │ ├── header.hpp.jinja2 │ ├── loader.jinja2 │ ├── null.cpp.jinja2 │ ├── null.hpp.jinja2 │ ├── numeric.cpp.jinja2 │ ├── numeric.hpp.jinja2 │ ├── object.cpp.jinja2 │ ├── object.hpp.jinja2 │ ├── oneof.cpp.jinja2 │ ├── oneof.hpp.jinja2 │ ├── propname.jinja2 │ ├── ref.hpp.jinja2 │ ├── source.cpp.jinja2 │ ├── string.cpp.jinja2 │ ├── string.hpp.jinja2 │ ├── string_enum.cpp.jinja2 │ └── string_enum.hpp.jinja2 │ ├── markdown │ ├── __init__.py │ ├── allOf.md.jinja2 │ ├── anyOf.md.jinja2 │ ├── array.md.jinja2 │ ├── boolean.md.jinja2 │ ├── description.md.jinja2 │ ├── integer.md.jinja2 │ ├── null.md.jinja2 │ ├── number.md.jinja2 │ ├── object.md.jinja2 │ ├── oneOf.md.jinja2 │ ├── schema.jinja2 │ └── string.md.jinja2 │ └── python │ ├── __init__.py │ ├── array.py.jinja2 │ ├── axxof.py.jinja2 │ ├── file.py.jinja2 │ ├── import.py.jinja2 │ ├── loader.jinja2 │ ├── null.py.jinja2 │ ├── object.py.jinja2 │ ├── oneof.py.jinja2 │ ├── primative.py.jinja2 │ ├── reference.py.jinja2 │ └── test.py.jinja2 ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | output/ 3 | *.egg-info/ 4 | dist/ 5 | build/ 6 | .vscode/ 7 | *~ 8 | *.pyc 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | # Install custom tools, runtimes, etc. 4 | # For example "bastet", a command-line tetris clone: 5 | # RUN brew install bastet 6 | # 7 | # More information: https://www.gitpod.io/docs/config-docker/ 8 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | tasks: 5 | - init: pip install -r ./requirements.txt 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | exclude *.git* 3 | global-exclude *.pyc 4 | global-exclude __pycache__ 5 | recursive-include templates *.jinja2 6 | include LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON-Schema Codegen 2 | 3 | This python library consumes JSON-Schema and generates C++ or Python code. It generates structures to hold the values defined in the schema, restricting the values according to the schema. 4 | 5 | ## Python Requirements for Code Generation 6 | 7 | These requirements should be satisfied when `pip3` installing `json-schema-codegen`. 8 | 9 | * python 3.7 10 | * jinja2 11 | * stringcase 12 | 13 | ## Installation 14 | 15 | ```sh 16 | pip3 install json-schema-codegen 17 | ``` 18 | 19 | ## C++ Generated Code 20 | 21 | ### Supported Schema Features in C++ code generation 22 | 23 | A C++ class is generated for each schema node according to the schema's `type` property. Schemas without a `type` property, with the exception of combining operators `*Of`, are not supported. 24 | 25 | * type: string 26 | * minLength 27 | * maxLength 28 | * pattern 29 | * format=date-time (enforces ISO8601 format) 30 | * format=uuid (enables string object to be populated with a uuid) 31 | * type: string with enum 32 | * type: integer 33 | * maximum 34 | * minimum 35 | * exclusiveMaximum 36 | * exclusiveMinimum 37 | * multipleOf 38 | * type: number 39 | * maximum 40 | * minimum 41 | * exclusiveMaximum 42 | * exclusiveMinimum 43 | * multipleOf 44 | * type: boolean 45 | * type: null 46 | * type: array 47 | * items 48 | * minItems 49 | * maxItems 50 | * type: object 51 | * properties 52 | * required 53 | * allOf 54 | * anyOf 55 | * oneOf 56 | 57 | ##### References 58 | 59 | `$ref` references are supported for array items, object properties, allOf, anyOf, and oneOf. However, the caller must provide a "resolver" class which translates the reference into a class name and namespace. 60 | 61 | ### Dependencies of the C++ generated code 62 | 63 | * boost (boost::optional and boost::variant among others) 64 | * rapidjson 1.1 65 | * C++11 66 | 67 | ### Usage 68 | See [example_usage.py](./examples/example_usage.py) for a more elaborate example on generating C++ code. 69 | 70 | ```py 71 | import jsonschemacodegen.cpp as cpp 72 | 73 | simpleResolver = cpp.SimpleResolver() 74 | output_dir = "/tmp" 75 | 76 | generator = cpp.GeneratorFromSchema(src_output_dir=output_dir, 77 | header_output_dir=output_dir, 78 | resolver=simpleResolver, 79 | namespace=[], 80 | src_usings=[]) 81 | 82 | sampleSchema = {"type": "string"} 83 | 84 | generator.Generate(sampleSchema, 'Example', 'example') 85 | ``` 86 | 87 | ## Python Generated Code 88 | 89 | A Python3 class is generated for each schema node; the class encapsulating the data described by the schema. The class accepts in its constructor python primative data types that match the format described the the schema. Each class has a `Serializable` method which returns data in a format that can be serialized. 90 | 91 | JSON (de-)serialization does not happen in the actual class. This allows for flexibility to use other line-formats, for example, YAML. 92 | 93 | ### Supported schema features for generating Python code 94 | 95 | * type: string 96 | * minLength 97 | * maxLength 98 | * pattern 99 | * enum 100 | * type: integer 101 | * maximum 102 | * minimum 103 | * exclusiveMaximum 104 | * exclusiveMinimum 105 | * multipleOf 106 | * enum 107 | * type: number 108 | * maximum 109 | * minimum 110 | * exclusiveMaximum 111 | * exclusiveMinimum 112 | * multipleOf 113 | * enum 114 | * type: boolean 115 | * type: null 116 | * type: array 117 | * items 118 | * minItems 119 | * maxItems 120 | * type: object 121 | * properties 122 | * required 123 | * allOf 124 | * anyOf 125 | * oneOf 126 | * Component schemas with the `title` property. 127 | 128 | ### Example usage for generating Python code 129 | 130 | For a more elaborate example, see [example_python.py](./examples/example_python.py) 131 | 132 | ```py 133 | from jsonschemacodegen import python as pygen 134 | import json 135 | 136 | with open('schema.json') as fp: 137 | generator = pygen.GeneratorFromSchema('output_dir') 138 | generator.Generate(json.load(fp), 'Example', 'example') 139 | ``` 140 | 141 | This example will create the file `output_dir/example.py` containing the Python3 class `Example` and nested classes as required. 142 | 143 | Using the generated code looks like this: 144 | ```py 145 | import example 146 | import json 147 | 148 | jsonText = '["an example string in an array"]' 149 | 150 | obj = example.Example(json.loads(jsonText)) 151 | 152 | print(json.dumps(obj, default=lambda x: x.Serializable())) 153 | ``` 154 | 155 | ## License 156 | 157 | GPLv2 158 | 159 | 160 | -------------------------------------------------------------------------------- /examples/example_asyncapi.py: -------------------------------------------------------------------------------- 1 | import jsonschemacodegen.cpp as cpp 2 | import jsonschemacodegen.resolver 3 | import sys 4 | import yaml 5 | import stringcase 6 | from pprint import pprint 7 | 8 | # This comes from the AsyncAPI Streetlights example 9 | # Only the 'components' section is shown because the 10 | # rest would be ignored. 11 | EXAMPLE_YAML=""" 12 | components: 13 | messages: 14 | lightMeasured: 15 | name: lightMeasured 16 | title: Light measured 17 | summary: Inform about environmental lighting conditions for a particular streetlight. 18 | contentType: application/json 19 | traits: 20 | - $ref: '#/components/messageTraits/commonHeaders' 21 | payload: 22 | $ref: "#/components/schemas/lightMeasuredPayload" 23 | turnOnOff: 24 | name: turnOnOff 25 | title: Turn on/off 26 | summary: Command a particular streetlight to turn the lights on or off. 27 | traits: 28 | - $ref: '#/components/messageTraits/commonHeaders' 29 | payload: 30 | $ref: "#/components/schemas/turnOnOffPayload" 31 | dimLight: 32 | name: dimLight 33 | title: Dim light 34 | summary: Command a particular streetlight to dim the lights. 35 | traits: 36 | - $ref: '#/components/messageTraits/commonHeaders' 37 | payload: 38 | $ref: "#/components/schemas/dimLightPayload" 39 | 40 | schemas: 41 | lightMeasuredPayload: 42 | type: object 43 | properties: 44 | lumens: 45 | type: integer 46 | minimum: 0 47 | description: Light intensity measured in lumens. 48 | sentAt: 49 | $ref: "#/components/schemas/sentAt" 50 | turnOnOffPayload: 51 | type: object 52 | properties: 53 | command: 54 | type: string 55 | enum: 56 | - "on" 57 | - "off" 58 | description: Whether to turn on or off the light. 59 | sentAt: 60 | $ref: "#/components/schemas/sentAt" 61 | dimLightPayload: 62 | type: object 63 | properties: 64 | percentage: 65 | type: integer 66 | description: Percentage to which the light should be dimmed to. 67 | minimum: 0 68 | maximum: 100 69 | sentAt: 70 | $ref: "#/components/schemas/sentAt" 71 | sentAt: 72 | type: string 73 | format: date-time 74 | description: Date and time when the message was sent. 75 | """ 76 | 77 | if __name__ == '__main__': 78 | 79 | spec = yaml.load(EXAMPLE_YAML) 80 | 81 | name = 'example' 82 | 83 | resolver = jsonschemacodegen.resolver.SimpleResolver('example') 84 | resolver.cpp_add_using(["asyncapi", name]) 85 | resolver.cpp_set_root_namespace(["asyncapi"]) 86 | output_dir = "output" 87 | 88 | generator = cpp.GeneratorFromSchema(src_output_dir=output_dir, 89 | header_output_dir=output_dir, 90 | resolver=resolver) 91 | 92 | for msgName, msg in spec['components']['messages'].items(): 93 | path = "{}#/components/messages/{}".format(name, msgName) 94 | generator.Generate(msg['payload'], path) 95 | 96 | for schemaName, schema in spec['components']['schemas'].items(): 97 | path = "{}#/components/schemas/{}".format(name, schemaName) 98 | generator.Generate(schema, path) 99 | 100 | -------------------------------------------------------------------------------- /examples/example_json.py: -------------------------------------------------------------------------------- 1 | from jsonschemacodegen.json_example import GeneratorFromSchema 2 | from jsonschemacodegen import schemawrappers 3 | 4 | if __name__ == '__main__': 5 | schema_structure = { 6 | "type": "object", 7 | "title": "outer structure", 8 | "properties": { 9 | "outerstr": {"type":"string", "example":"example outer string", "title":"outerstr"}, 10 | "outerbool": {"type":"boolean", "examples": [False,True], "title":"outerbool"}, 11 | "something": { 12 | "title": "somethingoneof", 13 | "oneOf": [ 14 | {"type": "integer", "examples":[12,2,3], "title": "oneof something is integer with three examples"}, 15 | { 16 | "type": "object", 17 | "title": "oneof something is object with two properties", 18 | "properties": { 19 | "innerone": { 20 | "title": "innerone property is oneof", 21 | "oneOf": [ 22 | {"type": "integer", "examples":[4,5], "title":"oneof innerone is integer with 2 examples"}, 23 | {"type": "boolean", "title": "oneof innerone is boolean"} 24 | ] 25 | }, 26 | "innertwo": {"type": "string", "example": "inner two", "title":"innertwo"} 27 | } 28 | } 29 | ] 30 | } 31 | }, 32 | "required": [ 33 | "outerbool" 34 | ] 35 | } 36 | schema = schemawrappers.SchemaFactory(schema_structure) 37 | generator = GeneratorFromSchema(None) 38 | examples = generator.GenerateSome(schema, number_of_examples=10) 39 | print(f"{len(examples)} number of examples:") 40 | for ex in examples: 41 | print(ex) -------------------------------------------------------------------------------- /examples/example_markdown.py: -------------------------------------------------------------------------------- 1 | from jsonschemacodegen import templator 2 | from jsonschemacodegen.resolver import SimpleResolver 3 | from jsonschemacodegen.schemawrappers import SchemaFactory 4 | 5 | schema = { 6 | "type" : "object", 7 | "properties": { 8 | "oneOfSomething": { 9 | 'oneOf': [ 10 | { 11 | 'type': 'integer', 12 | 'minimum': 0, 13 | 'multipleOf': 3, 14 | }, 15 | { 16 | "type": "string" 17 | }, 18 | { 19 | "type": "null" 20 | }, 21 | ] 22 | }, 23 | "myName": { 24 | "type": "string", 25 | "minLength": 1 26 | }, 27 | "anObject": { 28 | "type": "object", 29 | "properties": { 30 | "one": { 31 | "type": "string", 32 | "pattern": "[A-Z]*", 33 | }, 34 | "two": { 35 | "type": "number", 36 | }, 37 | } 38 | } 39 | }, 40 | "required": [ 41 | "myName" 42 | ] 43 | } 44 | 45 | 46 | 47 | if __name__ == '__main__': 48 | simpleResolver = SimpleResolver("myproject") 49 | t = templator.Generator('jsonschemacodegen.templates.markdown', 'output') 50 | schema = SchemaFactory(schema) 51 | t.RenderTemplate('description.md.jinja2', 'example.md', schema=schema, resolver=simpleResolver) 52 | -------------------------------------------------------------------------------- /examples/example_python.py: -------------------------------------------------------------------------------- 1 | from jsonschemacodegen import python as pygen 2 | import json 3 | 4 | if __name__ == '__main__': 5 | schema = { 6 | "type": "array", 7 | "items": { 8 | "oneOf": [ 9 | { 10 | "type": "string", 11 | "title": "StringOption", 12 | }, 13 | { 14 | "type": "integer", 15 | "title": "InterGerOption", 16 | }, 17 | { 18 | "type": "object", 19 | "title": "an object", 20 | "properties": { 21 | "astring": {"type": "string"}, 22 | "aboolean": {"type": "boolean"}, 23 | }, 24 | "required": [ 25 | "aboolean" 26 | ] 27 | }, 28 | { 29 | "allOf": [ 30 | { 31 | "type": "object", 32 | "properties": { 33 | "banana": {"type": "string"}, 34 | }, 35 | "required": ["banana"], 36 | }, 37 | { 38 | "type": "object", 39 | "properties": { 40 | "apple": {"type": "string"}, 41 | }, 42 | "required": ["apple"] 43 | } 44 | ] 45 | }, 46 | { 47 | "type": "null" 48 | } 49 | ] 50 | } 51 | } 52 | if True: 53 | generator = pygen.GeneratorFromSchema('output') 54 | generator.Generate(schema, 'Example', 'example') 55 | 56 | with open('output/__init__.py', 'w') as fp: 57 | pass 58 | 59 | from output import example 60 | 61 | def JsonPrint(o): 62 | print(json.dumps(o, default=lambda x: x.Serializable())) 63 | 64 | data = [ 65 | "a", 66 | 1, 67 | {"astring": "bool is true", "aboolean": True}, 68 | {"astring": "bool is false", "aboolean": False}, 69 | {"banana": "yello", "apple": "red"}, 70 | None, 71 | ] 72 | #exampleObj = example.Example(data) 73 | #assert(exampleObj[3].Get().GetAstring().Get() == data[3]['astring']) 74 | #assert(exampleObj[3].Get().GetAboolean().Get() == False) 75 | 76 | #JsonPrint(exampleObj) 77 | 78 | #f = example.Example() 79 | #JsonPrint(f) 80 | 81 | #nullObj1 = example.Example.Item.Option5() 82 | #nullObj2 = example.Example.Item.Option5(None) 83 | #nullObj3 = example.Example.Item.Option5(nullObj1) 84 | #g = example.Example.Item(nullObj3) 85 | #JsonPrint(g) 86 | 87 | #comboObject1 = example.Example.Item.Option4({"apple": "a", "banana": "b"}) 88 | #comboObject2 = example.Example.Item.Option4(comboObject1) 89 | #comboObject3 = example.Example.Item.Option4({"apple":"ap"}, {"banana", "ba"}) 90 | #comboObject4Comp1 = example.Example.Item.Option4.Component1(apple="ap") 91 | #comboObject3 = example.Example.Item.Option4(comboObject4Comp1, {"banana", "ba"}) 92 | #h = example.Example.Item(comboObject2) 93 | #JsonPrint(h) 94 | JsonPrint(example.Example.Item({"banana": "yello", "apple": "red"})) 95 | 96 | #objectThree1 = example.Example.Item.AnObjectOption(astring="jacob", aboolean=True) 97 | #objectThree2 = example.Example.Item.AnObjectOption({"astring":"jacob", "aboolean":True}) 98 | #objectThree3 = example.Example.Item.AnObjectOption(objectThree1) 99 | -------------------------------------------------------------------------------- /examples/example_usage.py: -------------------------------------------------------------------------------- 1 | import jsonschemacodegen.cpp as cpp 2 | from jsonschemacodegen.resolver import SimpleResolver 3 | 4 | if __name__ == '__main__': 5 | simpleResolver = SimpleResolver("myproject") 6 | output_dir = "output" 7 | 8 | # The generated C++ class will be in the namespace foo::bar (ie foo::bar::ClassName) 9 | namespace = ["foo", "bar"] 10 | 11 | # The generated source will be prefixed with 12 | # "using namespace foo::bar" and "using namespace std" 13 | usings = [["foo", "bar"], ["std"]] 14 | 15 | generator = cpp.GeneratorFromSchema(src_output_dir=output_dir, 16 | header_output_dir=output_dir, 17 | resolver=simpleResolver) 18 | 19 | schema = { 20 | "type": "object", 21 | "properties": { 22 | "aString": { 23 | "type": "string", 24 | }, 25 | "aStringEnum": { 26 | "type": "string", 27 | "enum": ["a", "b", "c"], 28 | }, 29 | "aNumber": { 30 | "type": "number", 31 | "exclusiveMaximum": 1.0, 32 | "exclusiveMinimum": 0.0, 33 | }, 34 | "anArray": { 35 | "type": "array", 36 | "items": { 37 | "type": "string", 38 | "maxLength": 10, 39 | }, 40 | "maxItems": 5 41 | }, 42 | "aNullValue": { 43 | "type": "null", 44 | }, 45 | "anExampleOfOneOf": { 46 | "oneOf": [ 47 | {"type": "integer"}, 48 | {"type": "boolean"}, 49 | ] 50 | }, 51 | "anExampleOfAnyOf": { 52 | "anyOf": [ 53 | { 54 | "type": "object", 55 | "properties": { 56 | "foo": { 57 | "type": "integer", 58 | } 59 | }, 60 | "required": [ 61 | "foo" 62 | ] 63 | }, 64 | { 65 | "type": "object", 66 | "properties": { 67 | "bar": { 68 | "type": "string", 69 | } 70 | }, 71 | }, 72 | ] 73 | }, 74 | "anExampleOfAllOf": { 75 | "allOf": [ 76 | { 77 | "type": "object", 78 | "properties": { 79 | "bunny": { 80 | "type": "integer", 81 | } 82 | }, 83 | "required": [ 84 | "bunny" 85 | ] 86 | }, 87 | { 88 | "type": "object", 89 | "properties": { 90 | "rabbit": { 91 | "type": "boolean", 92 | } 93 | }, 94 | }, 95 | ] 96 | }, 97 | } 98 | } 99 | 100 | print("Generated {}".format(generator.Generate(schema, "myproject#/example/object"))) 101 | 102 | schemaWithRefs = { 103 | "oneOf": [ 104 | { 105 | "type": "object", 106 | "properties": { 107 | "localReference": {"$ref": "#/components/schemas/localReference"}, 108 | "externalReference": {"$ref": "other.yaml#/components/schemas/externalReference"}, 109 | "arrayWithLocalRef": { 110 | "type": "array", 111 | "items": {"$ref": "#/components/schemas/localItem"}, 112 | }, 113 | }, 114 | }, 115 | { 116 | "type": "object", 117 | "properties": { 118 | "arrayWithExtRef": { 119 | "type": "array", 120 | "items": {"$ref": "other.yaml#/components/schemas/extItem"}, 121 | }, 122 | }, 123 | }, 124 | {"$ref": "other.yaml#/components/schemas/externalObject"}, 125 | {"$ref": "#/components/schemas/localObject"}, 126 | ] 127 | } 128 | print("Generated {}".format(generator.Generate(schemaWithRefs, "myproject#/example/objectFoo"))) 129 | 130 | dateTimeSchema = { 131 | "type": "string", 132 | "format": "date-time" 133 | } 134 | 135 | print("Generated {}".format(generator.Generate(dateTimeSchema, "myproject#/example/dateTimeObject"))) 136 | 137 | uuidSchema = { 138 | "type": "string", 139 | "format": "uuid" 140 | } 141 | 142 | print("Generated {}".format(generator.Generate(uuidSchema, "myproject#/example/uuidObject"))) -------------------------------------------------------------------------------- /jsonschemacodegen/__init__.py: -------------------------------------------------------------------------------- 1 | from . import _version 2 | 3 | __version__ = _version.__version__ 4 | __version_info__ = _version.__version_info__ 5 | -------------------------------------------------------------------------------- /jsonschemacodegen/_version.py: -------------------------------------------------------------------------------- 1 | __version_info__ = (0, 6, 3) 2 | __version__ = ".".join([str(x) for x in list(__version_info__)]) 3 | -------------------------------------------------------------------------------- /jsonschemacodegen/cpp.py: -------------------------------------------------------------------------------- 1 | import json 2 | import abc 3 | import stringcase 4 | import os 5 | 6 | from jacobsjinjatoo import templator 7 | from . import schemawrappers 8 | 9 | class ResolverBaseClass(abc.ABC): 10 | 11 | def cpp_resolve_namespace(self, ns: list, append='::') -> str: 12 | """ Figures out what a namespace should be for C++. 13 | @param ns is the full namespace of the object that should be resolved. For example, if the object is: 14 | d::e::f::Goo, then the ns param should be ['d', 'e', 'f'] with 'Goo' being the object name. 15 | @param append is a string that will be appended to the result only if there is some part of a namespace left. 16 | @returns a string that should go before the object. Using the described parameters, the result 17 | would be "f::" 18 | """ 19 | assert(isinstance(ns, list)) 20 | usings = self.cpp_get_usings() 21 | for n in ns: 22 | assert(isinstance(n, str)), "namespace is %s" % (ns) 23 | ret = ns 24 | nsString = "||".join(ns) 25 | for using in usings: 26 | if nsString.startswith("||".join(using)): 27 | abbr = ns[len(using):] 28 | if len(abbr) < len(ret): 29 | ret = abbr 30 | if len(ret) > 0: 31 | return "::".join(ret)+append 32 | else: 33 | return "" 34 | 35 | def append_to_namespace(self, ns: list, appendage) -> list: 36 | assert(isinstance(ns, list)), "Namespace isn't list it is %s" % (ns) 37 | newNs = ns.copy() 38 | newNs.append(appendage) 39 | return newNs 40 | 41 | @abc.abstractmethod 42 | def cpp_get_filename_base(self, reference): 43 | pass 44 | 45 | @abc.abstractmethod 46 | def cpp_get_header(self, reference: str) -> str: 47 | """ Given a $ref reference from a schema, return the name/path of the header file that should be included for the declaration 48 | of the represented object. 49 | """ 50 | pass 51 | 52 | @abc.abstractmethod 53 | def cpp_get_namespace(self, reference: str) -> list: 54 | """Given a reference and the current usings statements, return the namespace of the object pointed to by the reference. 55 | If the namespace is not empty, also append the append string 56 | """ 57 | pass 58 | 59 | @abc.abstractmethod 60 | def cpp_get_name(self, reference: str) -> str: 61 | """Given a reference, return the C++ object name pointed to by the reference. 62 | """ 63 | pass 64 | 65 | def cpp_get_ns_name(self, reference: str) -> str: 66 | ns = self.cpp_get_namespace(reference) 67 | name = self.cpp_get_name(reference) 68 | if ns is None: 69 | return 70 | return "{}::{}".format("::".join(ns), name) 71 | 72 | @abc.abstractmethod 73 | def cpp_get_root_namespace(self) -> list: 74 | pass 75 | 76 | @abc.abstractmethod 77 | def cpp_get_usings(self) -> list: 78 | pass 79 | 80 | @abc.abstractmethod 81 | def cpp_get_lib_ns(self) -> list: 82 | pass 83 | 84 | 85 | class GeneratorFromSchema(object): 86 | 87 | def __init__(self, src_output_dir, header_output_dir, resolver): 88 | self.output_dir = { 89 | "src": src_output_dir, 90 | "header": header_output_dir, 91 | } 92 | assert(isinstance(resolver, ResolverBaseClass)), "Resolver is %s" % (resolver) 93 | self.resolver = resolver 94 | 95 | def _make_sure_directory_exists(self, output_key, dir_path): 96 | d = os.path.join(self.output_dir[output_key], dir_path) 97 | if not os.path.exists(d): 98 | os.makedirs(d) 99 | 100 | def GetDeps(self, schema): 101 | return schema.CppIncludes(self.resolver) 102 | 103 | def Generate(self, schema, path): 104 | retval = [None, None] 105 | srcGenerator = templator.CodeTemplator(self.output_dir['src']).add_template_package('jsonschemacodegen.templates.cpp') 106 | headerGenerator = templator.CodeTemplator(self.output_dir['header']).add_template_package('jsonschemacodegen.templates.cpp') 107 | args = { 108 | "Name": self.resolver.cpp_get_name(path), 109 | "schema": schemawrappers.SchemaFactory(schema), 110 | } 111 | headerFilename = self.resolver.cpp_get_header(path) 112 | self._make_sure_directory_exists('header', os.path.dirname(headerFilename)) 113 | if '$ref' not in schema: 114 | srcFileName = "{}.cpp".format(self.resolver.cpp_get_filename_base(path)) 115 | self._make_sure_directory_exists('src', os.path.dirname(srcFileName)) 116 | srcGenerator.render_template(template_name="source.cpp.jinja2", 117 | output_name=srcFileName, 118 | deps=['"{}"'.format(self.resolver.cpp_get_header(path))], 119 | usings=self.resolver.cpp_get_usings(), 120 | ns=self.resolver.cpp_get_namespace(path), 121 | resolver=self.resolver, 122 | **args) 123 | retval[0] = srcFileName 124 | headerGenerator.render_template(template_name="header.hpp.jinja2", 125 | output_name=headerFilename, 126 | ns=self.resolver.cpp_get_namespace(path), 127 | deps=self.GetDeps(args['schema']), 128 | resolver=self.resolver, 129 | filename=headerFilename, 130 | filepath='', 131 | **args) 132 | retval[1] = headerFilename 133 | return tuple(retval) 134 | 135 | 136 | class LibraryGenerator(object): 137 | 138 | def __init__(self, src_output_dir: str, header_output_dir: str, resolver): 139 | self.output_dir = { 140 | "src": src_output_dir, 141 | "header": header_output_dir, 142 | } 143 | self.resolver = resolver 144 | 145 | def _make_sure_directory_exists(self, output_key, dir_path): 146 | d = os.path.join(self.output_dir[output_key], dir_path) 147 | if not os.path.exists(d): 148 | os.makedirs(d) 149 | 150 | def Generate(self): 151 | retval = [None, "exceptions.hpp"] 152 | headerGenerator = templator.CodeTemplator(self.output_dir['header']).add_template_package('jsonschemacodegen.templates.cpp') 153 | headerGenerator.render_template(template_name="exceptions.hpp.jinja2", 154 | output_name="exceptions.hpp", 155 | ns=self.resolver.cpp_get_lib_ns(), 156 | ) 157 | return tuple(retval) 158 | -------------------------------------------------------------------------------- /jsonschemacodegen/json_example.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import abc 4 | from copy import copy 5 | from . import schemawrappers 6 | 7 | class SchemaResolverBaseClass(abc.ABC): 8 | 9 | @abc.abstractmethod 10 | def get_schema(self, reference, root=None): 11 | """Given a reference, returns a wrapped schema object. 12 | """ 13 | pass 14 | 15 | @abc.abstractmethod 16 | def get_json(self, reference, root=None) -> dict: 17 | pass 18 | 19 | @abc.abstractmethod 20 | def get_document(self, reference): 21 | pass 22 | 23 | 24 | class GeneratorFromSchema(object): 25 | 26 | def __init__(self, resolver=None): 27 | self.resolver = resolver 28 | 29 | @staticmethod 30 | def DeDuplicate(aList : list, limit=None) -> list: 31 | text_list = [json.dumps(a, sort_keys=True) for a in aList] 32 | text_unique = [] 33 | for t in text_list: 34 | if len(text_unique) == limit: 35 | break 36 | if t not in text_unique: 37 | text_unique.append(t) 38 | text_sorted = sorted(text_unique, key=len) 39 | return [json.loads(s) for s in text_sorted] 40 | 41 | def GenerateSome(self, schema, number_of_examples=2, random_seed=0xBEEF) -> list: 42 | examples = [] 43 | indexes = [] 44 | random.seed(random_seed) 45 | number_of_combos = schema.GetExampleCombos(self.resolver) 46 | bits_for_combos = schemawrappers.bitsNeededForNumber(number_of_combos) 47 | index_max = 1 << bits_for_combos 48 | if number_of_examples >= index_max: 49 | indexes = [schemawrappers.ExampleIndex(i) for i in range(0, index_max)] 50 | elif (number_of_examples*3) >= index_max: 51 | indexes = [schemawrappers.ExampleIndex(i) for i in range(1, index_max)] 52 | random.shuffle(indexes) 53 | else: 54 | index_numbers = [] 55 | while len(indexes) < number_of_examples*3: 56 | rand_index = random.randint(1, index_max) 57 | if rand_index not in index_numbers: 58 | indexes.append(schemawrappers.ExampleIndex(rand_index)) 59 | index_numbers.append(rand_index) 60 | for index in indexes: 61 | ex = schema.Example(self.resolver, index) 62 | examples.append(ex) 63 | return self.DeDuplicate(examples, limit=number_of_examples) 64 | 65 | def GenerateFull(self, schema) -> list: 66 | index = schemawrappers.ExampleIndex(-1) 67 | return [schema.Example(self.resolver, index)] 68 | 69 | def GenerateLimited(self, schema) -> list: 70 | index = schemawrappers.ExampleIndex(0) 71 | return [schema.Example(self.resolver, index)] 72 | 73 | def Generate(self, schema, number_of_examples=3) -> list: 74 | examples = [] 75 | if number_of_examples > 0: 76 | ex = self.GenerateFull(schema) 77 | examples.extend(ex) 78 | if number_of_examples > 1: 79 | ex = self.GenerateLimited(schema) 80 | examples.extend(ex) 81 | if number_of_examples > 2: 82 | examples.extend(self.GenerateSome(schema, number_of_examples)) 83 | ret = self.DeDuplicate(examples, limit=number_of_examples) 84 | return ret 85 | 86 | -------------------------------------------------------------------------------- /jsonschemacodegen/python.py: -------------------------------------------------------------------------------- 1 | import json 2 | import abc 3 | import stringcase 4 | import os.path 5 | 6 | from jacobsjinjatoo import templator 7 | from . import schemawrappers 8 | from . import json_example 9 | 10 | class ResolverBaseClass(abc.ABC): 11 | 12 | @abc.abstractmethod 13 | def py_include_statement(self, reference): 14 | """Should return the include statement needed to acquire the object representing the 15 | schema pointed to at `reference`. Example, "from schema_foo import Foo" 16 | """ 17 | pass 18 | 19 | @abc.abstractmethod 20 | def py_class_name(self, reference): 21 | """Should return the class name for the object representing the schema pointed to at `reference`. 22 | For example, "schema_foo.Foo" 23 | """ 24 | pass 25 | 26 | @abc.abstractmethod 27 | def py_filename(self, reference): 28 | """Should return the name of the filename holding the python class representing the schema pointed to 29 | at `reference`. For example, "schema_foo.py" 30 | """ 31 | pass 32 | 33 | 34 | class GeneratorFromSchema(object): 35 | 36 | def __init__(self, output_dir, resolver=None): 37 | self.output_dir = output_dir 38 | self.resolver = resolver 39 | 40 | def GetDeps(self, schema): 41 | return [] 42 | 43 | def Generate(self, schema, root, class_name, filename_base): 44 | generator = templator.CodeTemplator(self.output_dir) 45 | generator.add_template_package('jsonschemacodegen.templates.python') 46 | 47 | args = { 48 | "Name": class_name, 49 | "schema": schemawrappers.SchemaFactory(schema, root), 50 | } 51 | return generator.render_template(template_name="file.py.jinja2", output_name="{}.py".format(filename_base), resolver=self.resolver, **args) 52 | 53 | def Examples(self, schema, root): 54 | wrapped_schema = schemawrappers.SchemaFactory(schema, root) 55 | number_of_examples = wrapped_schema.GetExampleCombos(self.resolver) 56 | examples = [] 57 | examples.append(wrapped_schema.Example(self.resolver, schemawrappers.ExampleIndex(0))) 58 | examples.append(wrapped_schema.Example(self.resolver, schemawrappers.ExampleIndex(-1))) 59 | show_examples = min(number_of_examples, 20) 60 | example_step = int(number_of_examples/show_examples) 61 | index = example_step 62 | for _ in range(0, show_examples): 63 | examples.append(wrapped_schema.Example(self.resolver, schemawrappers.ExampleIndex(index))) 64 | index += example_step 65 | return sorted(list(set([json.dumps(x) for x in examples]))) 66 | 67 | def GenerateTest(self, schema, root, class_name, filename_base, path): 68 | filename = self.resolver.py_test_filename(path) 69 | generator = templator.CodeTemplator(os.path.join(self.output_dir, os.path.dirname(filename))) 70 | generator.add_template_package('jsonschemacodegen.templates.python') 71 | wrapped_schema = schemawrappers.SchemaFactory(schema, root) 72 | args = { 73 | "Name": class_name.split('.')[-1], 74 | "schema": wrapped_schema, 75 | "examples": self.Examples(schema, root), 76 | "class": class_name, 77 | "path": path, 78 | "objType": path.split("/")[-2] 79 | } 80 | return generator.render_template(template_name="test.py.jinja2", output_name=os.path.basename(filename), resolver=self.resolver, **args) 81 | 82 | def GenerateFromPath(self, schema, path): 83 | assert(self.resolver) 84 | class_name = self.resolver.py_class_name(path).split('.')[-1] 85 | filename_base = self.resolver.py_filename(path) 86 | return self.Generate(schema, class_name, filename_base) 87 | 88 | def GenerateTestFromPath(self, schema, root, path): 89 | assert(self.resolver) 90 | class_name = self.resolver.py_class_name(path) 91 | filename_base = self.resolver.py_filename(path) 92 | return self.GenerateTest(schema, root, class_name, filename_base, path) -------------------------------------------------------------------------------- /jsonschemacodegen/resolver.py: -------------------------------------------------------------------------------- 1 | from . import cpp 2 | from . import python as pyschema 3 | from . import json_example as jsex 4 | import stringcase 5 | from . import schemawrappers 6 | import yaml 7 | from copy import copy 8 | 9 | class TreeWalkerException(Exception): 10 | pass 11 | 12 | class SimpleResolver(cpp.ResolverBaseClass, pyschema.ResolverBaseClass, jsex.SchemaResolverBaseClass): 13 | 14 | def __init__(self, uri, root=None): 15 | self.uri = uri 16 | self.root = root 17 | self.usings = [] 18 | self.rootNs = [] 19 | 20 | def cpp_set_root_namespace(self, ns: list): 21 | self.rootNs = ns 22 | 23 | def cpp_add_using(self, using: list): 24 | self.usings.append(using) 25 | 26 | def cpp_get_root_namespace(self) -> list: 27 | return self.rootNs 28 | 29 | def cpp_get_usings(self) -> list: 30 | return self.usings 31 | 32 | def _get_reference_parts(self, reference) -> tuple: 33 | pkg = None 34 | fn = reference.split('#')[0] or None 35 | path = reference.split('#')[1] 36 | parts = path.split('/') 37 | if fn: 38 | pkg = fn.split('.')[0] 39 | return (pkg, parts[-2], stringcase.pascalcase(parts[-1])) 40 | 41 | def _get_reference_parts(self, reference) -> dict: 42 | assert('#' in reference), "Reference '{}' seemed malformed".format(reference) 43 | url, path = reference.split('#') 44 | theType, name = path.split('/')[-2:] 45 | return { 46 | "url": url, 47 | "uri": url, 48 | "path": path, 49 | "type": theType, 50 | "PascalType": stringcase.pascalcase(theType), 51 | "name": name, 52 | "PascalName": stringcase.pascalcase(name), 53 | "snake_name": stringcase.snakecase(name), 54 | "pkg": url is not None and url.split(".")[0] or None, 55 | } 56 | 57 | def cpp_get_header_dir(self, uri=None) -> str: 58 | uri = uri or self.uri 59 | pkg = uri.split(".")[0] 60 | return pkg 61 | 62 | def cpp_get_filename_base(self, reference) -> str: 63 | parts = self._get_reference_parts(reference) 64 | if parts['pkg'] not in [None, self.uri]: 65 | header_dir = self.cpp_get_header_dir(parts['uri']) 66 | fn_base = "{0}/{1}_{2}".format(header_dir, parts['type'].rstrip('s'), parts['snake_name']) 67 | else: 68 | fn_base = "{0}_{1}".format(parts['type'].rstrip('s'), parts['snake_name']) 69 | return fn_base 70 | 71 | def cpp_get_header(self, reference) -> str: 72 | header = "{}.hpp".format(self.cpp_get_filename_base(reference)) 73 | return header 74 | 75 | def cpp_get_namespace(self, reference) -> list: 76 | parts = self._get_reference_parts(reference) 77 | ns = copy(self.rootNs) 78 | if parts['pkg'] is not None: 79 | ns.append(stringcase.lowercase(parts['pkg'])) 80 | else: 81 | ns.append(self.uri) 82 | ns.append(stringcase.lowercase(parts['type'].rstrip('s'))) 83 | return ns 84 | 85 | def cpp_get_name(self, reference) -> str: 86 | parts = self._get_reference_parts(reference) 87 | return parts['PascalName'] 88 | 89 | def py_include_statement(self, reference) -> str: 90 | ref = self._get_reference_parts(reference) 91 | if ref['pkg'] is not None: 92 | return "import {pkg}.{type}_{name}".format(**ref) 93 | else: 94 | return "import {type}_{name}".format(**ref) 95 | 96 | def py_class_name(self, reference) -> str: 97 | ref = self._get_reference_parts(reference) 98 | if ref['pkg'] is not None: 99 | return "{pkg}.{type}_{name}.{PascalName}".format(**ref) 100 | else: 101 | return "{type}_{name}.{PascalName}".format(**ref) 102 | 103 | def py_filename(self, reference) -> str: 104 | ref = self._get_reference_parts(reference) 105 | return "{type}_{name}.py".format(**ref) 106 | 107 | def _walk_through_tree(self, tree, path) -> dict: 108 | assert(tree is not None), "No tree to walk through" 109 | walker = tree 110 | for p in [p for p in path.split('/') if len(p) > 0]: 111 | if p not in walker: 112 | treeTitle = 'info' in tree and 'title' in tree['info'] and tree['info']['title'] or 'UNKNOWN' 113 | treeId = 'id' in tree and tree['id'] or treeTitle 114 | raise TreeWalkerException("Could not resolve {} from '{}'".format(path, treeId)) 115 | walker = walker[p] 116 | return walker 117 | 118 | def get_document(self, uri, encoding=None) -> dict: 119 | if '#' in uri: 120 | uri = uri.split('#')[0] 121 | with open(uri, 'r') as fp: 122 | if encoding == 'json' or (encoding is None and 'json' in uri): 123 | import json 124 | return json.load(fp) 125 | else: 126 | return yaml.load(fp, Loader=yaml.FullLoader) 127 | 128 | def _get_root(self, reference, root): 129 | parts = self._get_reference_parts(reference) 130 | if parts['uri']: 131 | root_doc = self.get_document(parts['uri']) 132 | elif root is not None: 133 | root_doc = root 134 | else: 135 | root_doc = self.root 136 | return root_doc 137 | 138 | def get_json(self, reference, root=None) -> dict: 139 | parts = self._get_reference_parts(reference) 140 | root_doc = self._get_root(reference, root) 141 | try: 142 | json_doc = self._walk_through_tree(root_doc, parts['path']) 143 | except TreeWalkerException as e: 144 | print(f"{self} Error in resolving {reference}: {e}") 145 | raise 146 | else: 147 | return json_doc 148 | 149 | def get_schema(self, reference, root=None) -> schemawrappers.SchemaBase: 150 | schemasRoot = self._get_root(reference, root) 151 | schema = schemawrappers.SchemaFactory(self.get_json(reference, root=root), schemasRoot) 152 | return schema 153 | 154 | def cpp_get_lib_ns(self): 155 | return [] -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pearmaster/json-schema-codegen/732829c2a4cce5e2189d75dc05fdd846dbdf3c66/jsonschemacodegen/templates/cpp/__init__.py -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/allof.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 2 | {%macro NestedObjectName(name) -%} 3 | {{className}}::{{-name | UpperCamelCase-}} 4 | {%-endmacro%} 5 | {%-macro ObjectType(propName, propSchema) -%} 6 | {%-if '$ref' in propSchema -%} 7 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 8 | {%-else-%} 9 | {{-NestedObjectName(propName)-}} 10 | {%-endif-%} 11 | {%-endmacro%} 12 | {%-macro ObjectName(propName, propSchema) -%} 13 | {%-if '$ref' in propSchema -%} 14 | {{-resolver.cpp_get_name(propSchema['$ref'])|UpperCamelCase-}} 15 | {%-else-%} 16 | {{-propName | UpperCamelCase-}} 17 | {%-endif-%} 18 | {%-endmacro%} 19 | {%-macro ComponentName(parentName, schema, i)-%} 20 | {%-if 'title' in schema -%} 21 | {{-schema.title|UpperCamelCase-}} 22 | {%-if schema.title|UpperCamelCase == parentName|UpperCamelCase-%}Component{%-endif-%} 23 | {%-if 'properties' in schema and schema.title in schema.properties-%}Object{%-endif-%} 24 | {%-elif 'oneOf' in schema%} 25 | {{-''-}}OneOfComponent{{-i-}} 26 | {%-elif 'type' in schema and schema['type'] == 'object' and schema['properties'] | length == 1 -%} 27 | {{-schema.PropertyKeys()[0] | UpperCamelCase-}}Component 28 | {%-else-%} 29 | {{-''-}}Component{{-i-}} 30 | {%-endif-%} 31 | {%-endmacro%} 32 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 33 | 34 | {%import 'loader.jinja2' as loader with context%} 35 | {%-for s in schema.allOf %}{%set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset%} 36 | {{loader.Class('cpp', resolver, [className], componentName, s) }} 37 | {%-endfor%} 38 | 39 | 40 | {{className}}::{{Name}}({%-for s in schema.allOf -%} 41 | {%-set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset-%} 42 | const {{ObjectType(componentName, s)}}& {{componentName|camelCase}}{%if not loop.last%}, {%endif-%} 43 | {%-endfor%}) : {%for s in schema.allOf -%} 44 | {%-set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset-%} 45 | {{componentName|privatize}}({{componentName|camelCase}}){%if not loop.last%}, {%endif-%} 46 | {%-endfor%} 47 | { 48 | 49 | } 50 | 51 | {%-for s in schema.allOf %}{%set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset%} 52 | {{ObjectType(componentName, s)}} {{className}}::Get{{ObjectName(componentName, s)}}() const 53 | { 54 | return {{componentName|privatize}}; 55 | } 56 | 57 | void {{className}}::Set{{ObjectName(componentName, s)}}(const {{ObjectType(componentName, s)}}& component) 58 | { 59 | {{componentName|privatize}} = component; 60 | {{componentName|privatize}}.SetHandle(_handle); 61 | } 62 | {%endfor%} 63 | 64 | {{className}} {{className}}::FromJson(const rapidjson::Value& json) 65 | { 66 | {%-if schema.requiredProperties | length > 0 %} 67 | ThrowIfMissingRequiredProperties(json); 68 | {%-endif%} 69 | {%-for s in schema.allOf %}{%set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset%} 70 | {{ObjectType(componentName, s)}} init{{componentName}} = {{ObjectType(componentName, s)}}::FromJson(json); 71 | {%-endfor%} 72 | 73 | return {{className}}({%-for s in schema.allOf %}{%set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset%}init{{componentName}}{%if not loop.last%}, {%endif%}{%endfor%}); 74 | } 75 | 76 | void {{className}}::ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const 77 | { 78 | if (!value.IsObject()) 79 | { 80 | value.SetObject(); 81 | } 82 | 83 | {%-for s in schema.allOf %}{%set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset%} 84 | {{componentName|privatize}}.ToJson(value, allocator); 85 | {%-endfor %} 86 | {%-if schema.requiredProperties | length > 0 %} 87 | ThrowIfMissingRequiredProperties(value); 88 | {%-endif%} 89 | } 90 | 91 | void {{className}}::SetHandle(const std::string& handle) 92 | { 93 | _handle = handle; 94 | 95 | // This 'allOf' object passes the handle through to its children objects 96 | {%for s in schema.allOf -%} 97 | {%-set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset-%} 98 | {{componentName|privatize}}.SetHandle(handle); 99 | {%-endfor%} 100 | } 101 | 102 | std::string {{className}}::GetHandle() const 103 | { 104 | return _handle; 105 | } 106 | 107 | {%-if schema.requiredProperties | length > 0 %} 108 | 109 | void {{className}}::ThrowIfMissingRequiredProperties(const rapidjson::Value& json) 110 | { 111 | if (!json.IsObject()) 112 | { 113 | throw {{exception}}("Not an object"); 114 | } 115 | {%-for reqProp in schema.requiredProperties | sort%} 116 | if (!json.HasMember("{{reqProp}}")) 117 | { 118 | throw {{exception}}("Missing '{{reqProp}}' property"); 119 | } 120 | {%-endfor%} 121 | } 122 | {%-endif%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/allof.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%macro NestedObjectName(name) -%} 2 | {{-name | UpperCamelCase-}} 3 | {%-endmacro%} 4 | {%-macro ObjectType(propName, propSchema) -%} 5 | {%-if '$ref' in propSchema -%} 6 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 7 | {%-else-%} 8 | {{-NestedObjectName(propName)-}} 9 | {%-endif-%} 10 | {%-endmacro%} 11 | {%-macro ObjectName(propName, propSchema) -%} 12 | {%-if '$ref' in propSchema -%} 13 | {{-resolver.cpp_get_name(propSchema['$ref'])|UpperCamelCase-}} 14 | {%-else-%} 15 | {{-NestedObjectName(propName)-}} 16 | {%-endif-%} 17 | {%-endmacro%} 18 | {%-macro ComponentName(parentName, schema, i)-%} 19 | {%-if 'title' in schema -%} 20 | {{-schema.title|UpperCamelCase-}} 21 | {%-if schema.title|UpperCamelCase == parentName|UpperCamelCase-%}Component{%-endif-%} 22 | {%-if 'properties' in schema and schema.title in schema.properties-%}Object{%-endif-%} 23 | {%-elif 'oneOf' in schema%} 24 | {{-''-}}OneOfComponent{{-i-}} 25 | {%-elif 'type' in schema and schema['type'] == 'object' and schema['properties'] | length == 1 -%} 26 | {{-schema.PropertyKeys()[0] | UpperCamelCase-}}Component 27 | {%-else-%} 28 | {{-''-}}Component{{-i-}} 29 | {%-endif-%} 30 | {%-endmacro%} 31 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 32 | 33 | /*! \class {{Name}} 34 | * \brief Mandatory union of: {%for s in schema.allOf %}{{ObjectName(ComponentName(Name, s, loop.index), s)}}{%if not loop.last%}, {%endif%}{%endfor %}. 35 | {%-if schema.description -%} 36 | * {{schema.description}} 37 | {%-endif%} 38 | {%-if schema.requiredProperties is defined and schema.requiredProperties | length > 0%} 39 | * The contained object must provide the following properties: {%for rp in schema.requiredProperties |sort%}`{{rp}}` {%endfor%} 40 | {%-endif%} 41 | */ 42 | class {{Name}} 43 | { 44 | public: 45 | {%-import 'loader.jinja2' as loader%} 46 | {%-for s in schema.allOf %} 47 | {%-if '$ref' not in s %} 48 | {{loader.Class('hpp', resolver, [Name], ComponentName(Name, s, loop.index), s) | indent(4) }} 49 | {%-endif%} 50 | {%endfor%} 51 | 52 | /*! Constructor for {{Name}} 53 | * Requires instances of all components. The data from each component is copied into storage for this class. 54 | {%-for s in schema.allOf -%} 55 | {%-set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset-%} 56 | * \param {{componentName|camelCase}} Instance of {{ObjectType(componentName, s)}} 57 | {%-endfor%} 58 | */ 59 | {{Name}}({%-for s in schema.allOf -%} 60 | {%-set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset-%} 61 | const {{ObjectType(componentName, s)}}& {{componentName|camelCase}}{%if not loop.last%}, {%endif-%} 62 | {%-endfor%}); 63 | virtual ~{{Name}}() = default; 64 | 65 | {%-for s in schema.allOf %}{%set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset%} 66 | /*! \fn {{ObjectType(componentName, s)}} Get{{ObjectName(componentName, s)}}() const 67 | * \brief returns the {{ObjectType(componentName, s)}} component of the {{Name}} object 68 | */ 69 | {{ObjectType(componentName, s)}} Get{{ObjectName(componentName, s)}}() const; 70 | 71 | /*! \fn void Set{{ObjectName(componentName, s)}}(const {{ObjectType(componentName, s)}}& component) 72 | * \brief Sets the {{ObjectType(componentName, s)}} component of {{Name}} object. 73 | * \param component is copied into the storage for this class. 74 | */ 75 | void Set{{ObjectName(componentName, s)}}(const {{ObjectType(componentName, s)}}& component); 76 | {%endfor%} 77 | 78 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 79 | * \brief Deserializes JSON into a new instance of the {{Name}} object. 80 | * \param json is the RapidJSON value which must be of object type and conforming to the schema. 81 | * \throw {{exception}} If any of the components don't deserialize correctly according to their schemas. 82 | * \returns {{Name}} 83 | */ 84 | static {{Name}} FromJson(const rapidjson::Value& json); 85 | 86 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 87 | * \brief Serializes the combined (allof) object to JSON 88 | * \param value is the RapidJSON value which will be modified to contain the serialization 89 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 90 | {%-if schema.requiredProperties is defined and schema.requiredProperties | length > 0%} 91 | * \throw {{exception}} When the resulting JSON object is missing one or more of the following properties: {%for rp in schema.requiredProperties |sort%}`{{rp}}` {%endfor%} 92 | {%-endif%} 93 | */ 94 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 95 | 96 | /*! Sets a string handle associated with this {{Name}} instance. 97 | * This gets called by a parent object after creating an instance that is used for an object's property. 98 | * \param handle is the string name. 99 | */ 100 | void SetHandle(const std::string& handle); 101 | 102 | /*! Gets the string handle associated with this {{Name}} instance. 103 | * This is often the property name used in a JSON-object parent. 104 | * It may be empty. 105 | * \returns the handle string 106 | */ 107 | std::string GetHandle() const; 108 | private: 109 | {%-for s in schema.allOf %}{%set componentName%}{{ComponentName(Name, s, loop.index)}}{%endset%} 110 | {{ObjectType(componentName, s)}} {{componentName|privatize}}; 111 | {%-endfor%} 112 | std::string _handle; 113 | 114 | {%-if schema.requiredProperties | length > 0 %} 115 | 116 | /*! Checks to make sure any required properties defined in the 'allOf' list are missing. 117 | * \param json Is completed JSON-object structure. 118 | * \throws {{exception}} If a property is missing 119 | */ 120 | static void ThrowIfMissingRequiredProperties(const rapidjson::Value& json); 121 | {%-endif%} 122 | }; 123 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/anyof.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 2 | {%macro NestedObjectName(name) -%} 3 | {{className}}::{{-name | UpperCamelCase-}} 4 | {%-endmacro%} 5 | {%-macro ObjectType(propName, propSchema) -%} 6 | {%-if '$ref' in propSchema -%} 7 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 8 | {%-else-%} 9 | {{-NestedObjectName(propName)-}} 10 | {%-endif-%} 11 | {%-endmacro%} 12 | {%-macro ObjectName(propName, propSchema) -%} 13 | {%-if '$ref' in propSchema -%} 14 | {{-resolver.cpp_get_name(propSchema['$ref'])|UpperCamelCase-}} 15 | {%-else-%} 16 | {{-propName | UpperCamelCase-}} 17 | {%-endif-%} 18 | {%-endmacro%} 19 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 20 | 21 | {%import 'loader.jinja2' as loader with context%} 22 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 23 | {{loader.Class('cpp', resolver, [className], componentName, s) }} 24 | {%-endfor%} 25 | 26 | 27 | {{className}}::{{Name}}() 28 | { 29 | 30 | } 31 | 32 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 33 | boost::optional<{{ObjectType(componentName, s)}}> {{className}}::Get{{ObjectName(componentName, s)}}() const 34 | { 35 | return {{componentName|privatize}}; 36 | } 37 | 38 | void {{className}}::Set{{ObjectName(componentName, s)}}(const {{ObjectType(componentName, s)}}& component) 39 | { 40 | {{componentName|privatize}} = component; 41 | {{componentName|privatize}}->SetHandle(_handle); 42 | } 43 | {%endfor%} 44 | 45 | {{className}} {{className}}::FromJson(const rapidjson::Value& json) 46 | { 47 | {%-if schema.requiredProperties | length > 0 %} 48 | ThrowIfMissingRequiredProperties(json); 49 | {%-endif%} 50 | {{className}} returnObject; 51 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 52 | try 53 | { 54 | returnObject.Set{{ObjectName(componentName, s)}}({{ObjectType(componentName, s)}}::FromJson(json)); 55 | } 56 | catch (...) 57 | { 58 | // If the type didn't parse, then no big deal since AnyOf doesn't require it to 59 | } 60 | {%-endfor%} 61 | 62 | return returnObject; 63 | } 64 | 65 | void {{className}}::ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const 66 | { 67 | if (!value.IsObject()) 68 | { 69 | value.SetObject(); 70 | } 71 | 72 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 73 | if ({{componentName|privatize}}) 74 | { 75 | {{componentName|privatize}}->ToJson(value, allocator); 76 | } 77 | {%-endfor %} 78 | {%-if schema.requiredProperties | length > 0 %} 79 | ThrowIfMissingRequiredProperties(value); 80 | {%-endif%} 81 | } 82 | 83 | void {{className}}::SetHandle(const std::string& handle) 84 | { 85 | _handle = handle; 86 | 87 | // This 'anyOf' object passes the handle through to its children objects 88 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 89 | if ({{componentName|privatize}}) 90 | { 91 | {{componentName|privatize}}->SetHandle(handle); 92 | } 93 | {%-endfor %} 94 | } 95 | 96 | std::string {{className}}::GetHandle() const 97 | { 98 | return _handle; 99 | } 100 | 101 | {%if schema.requiredProperties | length > 0 %} 102 | void {{className}}::ThrowIfMissingRequiredProperties(const rapidjson::Value& json) 103 | { 104 | if (!json.IsObject()) 105 | { 106 | throw {{exception}}("Not an object"); 107 | } 108 | {%-for reqProp in schema.requiredProperties |sort %} 109 | if (!json.HasMember("{{reqProp}}")) 110 | { 111 | throw {{exception}}("Missing '{{reqProp}}' property"); 112 | } 113 | {%-endfor%} 114 | } 115 | {%-endif%} 116 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/anyof.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%macro NestedObjectName(name) -%} 2 | {{-name | UpperCamelCase-}} 3 | {%-endmacro%} 4 | {%-macro ObjectType(propName, propSchema) -%} 5 | {%-if '$ref' in propSchema -%} 6 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 7 | {%-else-%} 8 | {{-NestedObjectName(propName)-}} 9 | {%-endif-%} 10 | {%-endmacro%} 11 | {%-macro ObjectName(propName, propSchema) -%} 12 | {%-if '$ref' in propSchema -%} 13 | {{-resolver.cpp_get_name(propSchema['$ref'])|UpperCamelCase-}} 14 | {%-else-%} 15 | {{-NestedObjectName(propName)-}} 16 | {%-endif-%} 17 | {%-endmacro%} 18 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 19 | 20 | /*! \class {{Name}} 21 | * \brief Optional union of: {%for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%}{{ObjectName(componentName, s)}}{%if not loop.last%}, {%endif%}{%endfor %} 22 | {%-if schema.description %} 23 | * {{schema.description}} 24 | {%-endif%} 25 | */ 26 | class {{Name}} 27 | { 28 | public: 29 | {%-import 'loader.jinja2' as loader%} 30 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 31 | {{loader.Class('hpp', resolver, [Name], componentName, s) | indent(4) }} 32 | 33 | {%endfor%} 34 | 35 | /*! Constructor for {{Name}} 36 | * Since all components are optional, this is an empty constructor. 37 | */ 38 | {{Name}}(); 39 | virtual ~{{Name}}() = default; 40 | 41 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 42 | /*! \fn boost::optional<{{ObjectType(componentName, s)}}> Get{{ObjectName(componentName, s)}}() const 43 | * \brief Gets the {{ObjectType(componentName, s)}} component of the {{Name}} object, if present. 44 | */ 45 | boost::optional<{{ObjectType(componentName, s)}}> Get{{ObjectName(componentName, s)}}() const; 46 | 47 | /*! \fn void Set{{ObjectName(componentName, s)}}(const {{ObjectType(componentName, s)}}& component) 48 | * \brief Sets the {{ObjectType(componentName, s)}} component of {{Name}} object. 49 | * \param {{ObjectType(componentName, s)}} component is copied into the storage for this class. 50 | */ 51 | void Set{{ObjectName(componentName, s)}}(const {{ObjectType(componentName, s)}}& component); 52 | {%endfor%} 53 | 54 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 55 | * \brief Deserializes JSON into a new instance of the {{Name}} object. 56 | * \param json is the RapidJSON value which must be of object type and conforming to the schema. 57 | * \returns {{Name}} 58 | */ 59 | static {{Name}} FromJson(const rapidjson::Value& json); 60 | 61 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 62 | * \brief Serializes the combination of any set components to JSON 63 | * \param value is the RapidJSON value which will be modified to contain the serialization 64 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 65 | */ 66 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 67 | 68 | /*! Sets a string handle associated with this {{Name}} instance. 69 | * This gets called by a parent object after creating an instance that is used for an object's property. 70 | * \param handle is the string name. 71 | */ 72 | void SetHandle(const std::string& handle); 73 | 74 | /*! Gets the string handle associated with this {{Name}} instance. 75 | * This is often the property name used in a JSON-object parent. 76 | * It may be empty. 77 | * \returns the handle string 78 | */ 79 | std::string GetHandle() const; 80 | private: 81 | {%-for s in schema.anyOf %}{%set componentName%}Component{{loop.index}}{%endset%} 82 | boost::optional<{{ObjectType(componentName, s)}}> {{componentName|privatize}}; 83 | {%-endfor%} 84 | std::string _handle; 85 | 86 | {%-if schema.requiredProperties | length > 0 %} 87 | 88 | /*! Checks to make sure any required properties defined in the 'anyOf' list are missing. 89 | * \param json Is completed JSON-object structure. 90 | * \throws {{exception}} If a property is missing 91 | */ 92 | static void ThrowIfMissingRequiredProperties(const rapidjson::Value& json); 93 | {%-endif%} 94 | }; 95 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/array.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%import 'loader.jinja2' as loader with context-%} 2 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 3 | {%-set std = resolver.cpp_resolve_namespace(['std']) %} 4 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 5 | {%-if schema.GetItemSchema()['$ref'] %} 6 | // Array uses items reference 7 | {%-set itemtype = loader.Reference(resolver, schema.GetItemSchema()['$ref']) %} 8 | {%-else%} 9 | {%-set itemtype = className+'::Item'%} 10 | 11 | {{loader.Class('cpp', 12 | resolver, 13 | resolver.append_to_namespace(ns, Name), 14 | 'Item', 15 | schema.GetItemSchema()) }} 16 | {%endif%} 17 | 18 | {{className}}::{{Name}}({{resolver.cpp_resolve_namespace(['std'])}}vector<{{itemtype}}> arr) 19 | { 20 | SetArray(arr); 21 | } 22 | 23 | {%if schema.minItems is not defined or schema.minItems == 0 -%} 24 | {{className}}::{{Name}}() 25 | { 26 | 27 | } 28 | {%-endif%} 29 | 30 | void {{className}}::SetArray(const {{resolver.cpp_resolve_namespace(['std'])}}vector<{{itemtype}}>& arr) 31 | { 32 | {%-if schema.maxItems is defined %} 33 | if (arr.size() > {{schema.maxItems}}) 34 | { 35 | throw {{exception}}("The array is shorter than {{className}}::MIN_ITEMS={{schema.minItems}}"); 36 | } 37 | {%-endif%} 38 | {%-if schema.minItems is defined %} 39 | if (arr.size() < {{schema.minItems}}) 40 | { 41 | throw {{exception}}("The array is longer than {{className}}::MAX_ITEMS={{schema.maxItems}}"); 42 | } 43 | {%-endif%} 44 | _arr = arr; 45 | for ({{itemtype}}& el : _arr) 46 | { 47 | el.SetHandle(_handle); 48 | } 49 | } 50 | 51 | {{resolver.cpp_resolve_namespace(['std'])}}vector<{{itemtype}}> {{className}}::GetArray() const 52 | { 53 | return _arr; 54 | } 55 | 56 | void {{className}}::Append(const {{itemtype}}& item) 57 | { 58 | {%-if schema.maxItems is defined %} 59 | if (_arr.size() == {{schema.maxItems}}) 60 | { 61 | throw {{std}}out_of_range("Adding to {{className}} would cause it to be longer than {{className}}::MAX_ITEMS={{schema.maxItems}}"); 62 | } 63 | {%-endif%} 64 | _arr.push_back(item); 65 | _arr.back().SetHandle(_handle); 66 | } 67 | 68 | {{className}} {{className}}::FromJson(const {{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& json) 69 | { 70 | if (!json.IsArray()) 71 | { 72 | throw {{exception}}("The JSON wasn't an array"); 73 | } 74 | {{resolver.cpp_resolve_namespace(['std'])}}vector<{{itemtype}}> arr; 75 | {{exception}}Collection exceptionCollection; 76 | unsigned i = 0; 77 | for (auto& v : json.GetArray()) 78 | { 79 | try 80 | { 81 | arr.push_back({{itemtype}}::FromJson(v)); 82 | } 83 | catch (const {{exception}}& e) 84 | { 85 | exceptionCollection.AddException(e, std::to_string(i)); 86 | } 87 | i++; 88 | } 89 | if (exceptionCollection.IsExceptional()) 90 | { 91 | throw exceptionCollection; 92 | } 93 | return {{className}}(arr); 94 | } 95 | 96 | void {{className}}::ToJson({{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 97 | { 98 | value.SetArray(); 99 | for (const {{itemtype}}& el : _arr) 100 | { 101 | {{resolver.cpp_resolve_namespace(['rapidjson'])}}Value elementValue; 102 | el.ToJson(elementValue, allocator); 103 | value.PushBack(elementValue, allocator); 104 | } 105 | } 106 | 107 | void {{className}}::SetHandle(const std::string& handle) 108 | { 109 | _handle = handle; 110 | 111 | for ({{itemtype}}& el : _arr) 112 | { 113 | el.SetHandle(handle); 114 | } 115 | } 116 | 117 | std::string {{className}}::GetHandle() const 118 | { 119 | return _handle; 120 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/array.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader-%} 2 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 3 | {%-if schema.GetItemSchema()['$ref'] %} 4 | {%-set itemtype = loader.Reference(resolver, schema.GetItemSchema()['$ref']) %} 5 | {%-else-%} 6 | {%-set itemtype = 'Item'-%} 7 | {%-endif%} 8 | /*! \class {{Name}} 9 | * \brief Wrapper around an array containing {{itemtype}} 10 | {%-if schema.description %} 11 | * {{schema.description}} 12 | {%-endif%} 13 | */ 14 | class {{Name}} 15 | { 16 | public: 17 | {%-if schema.GetItemSchema()['$ref'] is not defined %} 18 | {{loader.Class('hpp', resolver, [Name], 'Item', schema.GetItemSchema()) | indent(4) }} 19 | {%-endif%} 20 | 21 | {%-if schema.maxItems is defined %} 22 | static constexpr int MAX_ITEMS = {{schema.maxItems}}; 23 | {%-endif%} 24 | {%-if schema.minItems is defined %} 25 | static constexpr int MIN_ITEMS = {{schema.minItems}}; 26 | {%-endif%} 27 | 28 | /*! Constructor for {{Name}} 29 | * \param arr Vector of {{itemtype}} for initialization. {%if schema.minItems%}Must have at least {{schema.minItems}} elements.{%endif%}{%if schema.maxItems%}Must have at least {{schema.maxItems}} elements.{%endif%} 30 | * \throw {{exception}} if the length of the vector is incorrect 31 | */ 32 | {{Name}}(std::vector<{{itemtype}}> arr); 33 | 34 | {%-if schema.minItems is not defined or schema.minItems == 0 %} 35 | /*! Constructor for {{Name}} 36 | * Constructs an empty array object 37 | */ 38 | {{Name}}(); 39 | {%-endif%} 40 | 41 | virtual ~{{Name}}() = default; 42 | 43 | /*! \fn SetArray(const std::vector<{{itemtype}}>& arr) 44 | * \param arr resets the array to the provided vector {%if schema.minItems%}Must have at least {{schema.minItems}} elements.{%endif%}{%if schema.maxItems%}Must have at least {{schema.maxItems}} elements.{%endif%} 45 | {%-if schema.minItems is defined or schema.maxItems is defined %} 46 | * \throw {{exception}} if the length of the vector is incorrect 47 | {%-endif%} 48 | */ 49 | void SetArray(const std::vector<{{itemtype}}>& arr); 50 | 51 | /*! \fn std::vector<{{itemtype}}> GetArray() const 52 | * \brief Returns a vector of all the {{itemtype}} items 53 | * \return std::vector<{{itemtype}}> 54 | */ 55 | std::vector<{{itemtype}}> GetArray() const; 56 | 57 | /*! \fn void Append(const {{itemtype}}& item) 58 | * \brief Adds an {{itemtype}} item to the array 59 | * \param item for appending 60 | {%-if schema.maxItems is defined %} 61 | * \throw std::out_of_range if adding the item would cause the length to be greater than {{Name}}::MAX_ITEMS={{schema.maxItems}} 62 | {%-endif%} 63 | */ 64 | void Append(const {{itemtype}}& item); 65 | 66 | {# TODO: Allow this class to be iteratable #} 67 | 68 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 69 | * \brief Deserializes JSON into a new instance of the {{Name}} object. 70 | * \param json is the RapidJSON value which must be of array type and conforming to the schema. 71 | * \throw {{exception}} If the JSON value isnt an array. 72 | * \throw {{exception}}Collection If any of the components dont deserialize correctly according to their schemas. 73 | * \returns {{Name}} 74 | */ 75 | static {{Name}} FromJson(const rapidjson::Value& json); 76 | 77 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 78 | * \brief Serializes the array and its elements to JSON 79 | * \param value is the RapidJSON value which will be modified to contain the serialization 80 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 81 | */ 82 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 83 | 84 | /*! Sets a string handle associated with this {{Name}} instance. 85 | * This gets called by a parent object after creating an instance that is used for an object's property. 86 | * \param handle is the string name. 87 | */ 88 | void SetHandle(const std::string& handle); 89 | 90 | /*! Gets the string handle associated with this {{Name}} instance. 91 | * This is often the property name used in a JSON-object parent. 92 | * It may be empty. 93 | * \returns the handle string 94 | */ 95 | std::string GetHandle() const; 96 | private: 97 | std::vector<{{itemtype}}> _arr; 98 | std::string _handle; 99 | }; 100 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/bool.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set std = resolver.cpp_resolve_namespace(['std']) %} 2 | {%-set rapidjson = resolver.cpp_resolve_namespace(['rapidjson']) %} 3 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 4 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 5 | {{className}}::{{Name}}(bool value) 6 | { 7 | Set(value); 8 | } 9 | 10 | {%-if schema.default is defined %} 11 | {%set emptyConstructor = true %} 12 | {{className}}::{{Name}}() 13 | { 14 | Set({{schema.default|lower}}); 15 | } 16 | {%-endif%} 17 | 18 | {{className}}::operator bool() const 19 | { 20 | return Get(); 21 | } 22 | 23 | {{className}}& {{className}}::operator=(bool value) 24 | { 25 | Set(value); 26 | return *this; 27 | } 28 | 29 | void {{className}}::Set(bool value) 30 | { 31 | _value = value; 32 | } 33 | 34 | bool {{className}}::Get() const 35 | { 36 | return _value; 37 | } 38 | 39 | {{className}} {{className}}::FromJson(const {{rapidjson}}Value& json) 40 | { 41 | if (!(json.IsBool())) 42 | { 43 | throw {{exception}}("Not a boolean"); 44 | } 45 | 46 | {{className}} newObject(json.GetBool()); 47 | return newObject; 48 | } 49 | 50 | void {{className}}::ToJson({{rapidjson}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 51 | { 52 | value.SetBool(_value); 53 | } 54 | 55 | void {{className}}::SetHandle(const std::string& handle) 56 | { 57 | _handle = handle; 58 | } 59 | 60 | std::string {{className}}::GetHandle() const 61 | { 62 | return _handle; 63 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/bool.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 2 | /*! {{Name}} is a wrapper around a boolean. 3 | {%-if schema.description %} 4 | * {{schema.description}} 5 | {%-endif%} 6 | */ 7 | class {{Name}} 8 | { 9 | public: 10 | /*! \fn {{Name}}(bool value) 11 | * \brief Constructor with initial value 12 | * \param value initial value 13 | */ 14 | {{Name}}(bool value); 15 | 16 | {%-if schema.default is defined %} 17 | /*! \fn {{Name}}() 18 | * \brief Constructor that uses default value of {{schema.default}} 19 | */ 20 | {{Name}}(); 21 | {%-endif%} 22 | 23 | virtual ~{{Name}}() = default; 24 | 25 | /*! Cast to bool operator 26 | */ 27 | operator bool() const; 28 | 29 | /*! Assign from bool operator 30 | */ 31 | {{Name}}& operator=(bool value); 32 | 33 | /*! \fn void Set(bool value) 34 | * \brief reset value to new bool 35 | * \param value new value 36 | */ 37 | void Set(bool value); 38 | 39 | /*! \fn bool Get() const 40 | * \brief get current boolean value 41 | */ 42 | bool Get() const; 43 | 44 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 45 | * \brief Deserializes JSON into a new instance of the {{Name}} object. 46 | * \param json is the RapidJSON value which must be a boolean type. 47 | * \throw {{exception}} If the JSON data is not a boolean 48 | * \returns {{Name}} 49 | */ 50 | static {{Name}} FromJson(const rapidjson::Value& json); 51 | 52 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 53 | * \brief Serializes boolean value to JSON 54 | * \param value is the RapidJSON value which will be modified to contain the serialization 55 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 56 | */ 57 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 58 | 59 | /*! Sets a string handle associated with this {{Name}} instance. 60 | * This gets called by a parent object after creating an instance that is used for an object's property. 61 | * \param handle is the string name. 62 | */ 63 | void SetHandle(const std::string& handle); 64 | 65 | /*! Gets the string handle associated with this {{Name}} instance. 66 | * This is often the property name used in a JSON-object parent. 67 | * It may be empty. 68 | * \returns the handle string 69 | */ 70 | std::string GetHandle() const; 71 | private: 72 | bool _value; 73 | std::string _handle; 74 | }; -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/constraints.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro ExprName(constraint)-%} 2 | {%-if constraint == 'default'-%} 3 | VALUE_DEFAULT 4 | {%-elif constraint == 'minimum'-%} 5 | VALUE_MINIMUM 6 | {%-elif constraint == 'maximum'-%} 7 | VALUE_MAXIMUM 8 | {%-elif constraint == 'exclusiveMinimum'-%} 9 | VALUE_EXCLUSIVE_MINIMUM 10 | {%-elif constraint == 'exclusiveMaximum'-%} 11 | VALUE_EXCLUSIVE_MAXIMUM 12 | {%-elif constraint == 'multipleOf'-%} 13 | VALUE_MULTIPLE_OF 14 | {%-elif constraint == 'const'-%} 15 | VALUE_CONSTANT 16 | {%-else-%} 17 | VALUE_{{constraint | CONST_CASE}} 18 | {%-endif-%} 19 | {%-endmacro-%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/datetime.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 2 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 3 | {%-set std = resolver.cpp_resolve_namespace(['std']) %} 4 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 5 | {{className}}::{{Name}}(const {{std}}string& value) 6 | { 7 | Set(value); 8 | } 9 | 10 | {{className}}::{{Name}}(const char* value) 11 | { 12 | Set(value); 13 | } 14 | 15 | {{className}}::{{Name}}() 16 | { 17 | {%-if schema.default is defined %} 18 | Set("{{schema.default}}"); 19 | {%-endif%} 20 | } 21 | 22 | {{className}}::{{Name}}(const {{Name}}& other) 23 | { 24 | _value = other._value; 25 | } 26 | 27 | {{className}}::operator {{std}}string() const 28 | { 29 | return GetString(); 30 | } 31 | 32 | {{className}}& {{className}}::operator=(const {{std}}string& value) 33 | { 34 | Set(value); 35 | return *this; 36 | } 37 | 38 | {{className}}& {{className}}::operator=(const char* value) 39 | { 40 | Set(value); 41 | return *this; 42 | } 43 | 44 | {{className}}& {{className}}::operator=(const boost::posix_time::ptime& value) 45 | { 46 | Set(value); 47 | return *this; 48 | } 49 | {%for origNs in originalNamespace %} 50 | namespace {{origNs}} { 51 | {%-endfor%} 52 | {{std}}ostream& operator<<({{std}}ostream& os, const {{className}}& obj) 53 | { 54 | os << obj.GetString(); 55 | return os; 56 | } 57 | {%for origNs in originalNamespace %}}{%-endfor%} // end namespaces 58 | 59 | void {{className}}::Set(const {{std}}string& value) 60 | { 61 | try 62 | { 63 | {{std}}string fixedValue = boost::replace_nth_copy(value, "T", 0, " "); 64 | Set(boost::posix_time::time_from_string(fixedValue)); 65 | } 66 | catch (const std::exception& e) 67 | { 68 | throw {{exception}}({{std}}string("Could not parse timestamp: ") + e.what()); 69 | } 70 | } 71 | 72 | void {{className}}::Set(const char* value) 73 | { 74 | Set(std::string(value)); 75 | } 76 | 77 | void {{className}}::Set(const boost::posix_time::ptime& datetime) 78 | { 79 | _value = datetime; 80 | } 81 | 82 | void {{className}}::SetNow() 83 | { 84 | Set(boost::posix_time::microsec_clock::universal_time()); 85 | } 86 | 87 | void {{className}}::SetCurrent{{schema.format | PascalCase}}() 88 | { 89 | _value = boost::none; 90 | } 91 | 92 | std::string {{className}}::GetString() const 93 | { 94 | return boost::posix_time::to_iso_extended_string(Get()); 95 | } 96 | 97 | bool {{className}}::IsCurrentTime() const 98 | { 99 | return !(_value); 100 | } 101 | 102 | boost::posix_time::ptime {{className}}::Get() const 103 | { 104 | if (IsCurrentTime()) 105 | { 106 | return boost::posix_time::microsec_clock::universal_time(); 107 | } 108 | return *_value; 109 | } 110 | 111 | {{className}} {{className}}::FromJson(const {{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& json) 112 | { 113 | if (!(json.IsString())) 114 | { 115 | throw {{exception}}("Not a string"); 116 | } 117 | 118 | return {{className}}(json.GetString()); 119 | } 120 | 121 | {{className}} {{className}}::FromString(const {{std}}string& str) 122 | { 123 | return {{className}}(str); 124 | } 125 | 126 | void {{className}}::ToJson({{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 127 | { 128 | std::string tempVal(GetString()); 129 | value.SetString(tempVal.c_str(), tempVal.size(), allocator); 130 | } 131 | 132 | void {{className}}::SetHandle(const std::string& handle) 133 | { 134 | _handle = handle; 135 | } 136 | 137 | std::string {{className}}::GetHandle() const 138 | { 139 | return _handle; 140 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/datetime.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 2 | /*! {{Name}} is a wrapper around a {{schema.format}} implemented as a {{schema.type}}. 3 | {%-if schema.description %} 4 | * {{schema.description}} 5 | {%-endif%} 6 | */ 7 | class {{Name}} 8 | { 9 | public: 10 | /*! Constructor. 11 | * \param value ISO8601 string representation of {{schema.format}} to be used for the initial value of {{Name}} instance. 12 | */ 13 | {{Name}}(const std::string& value); 14 | 15 | /*! Constructor. 16 | * \param value ISO8601 string representation of {{schema.format}} to be used for the initial value of {{Name}} instance. 17 | */ 18 | {{Name}}(const char* value); 19 | 20 | {%-if schema.default is defined %} 21 | /*! Constructor that uses the default value of "{{schema.default}}". 22 | */ 23 | {%-else%} 24 | /*! Constructor that sets the objects value to the current date and time. 25 | */ 26 | {%-endif%} 27 | {{Name}}(); 28 | 29 | /*! Copy Constructor. 30 | * \param other is the source of the time for the new object. 31 | */ 32 | {{Name}}(const {{Name}}& other); 33 | 34 | /*! Default destructor. 35 | */ 36 | virtual ~{{Name}}() = default; 37 | 38 | /*! Operator to cast a {{Name}} instance to a string formatted as a ISO8601. 39 | */ 40 | operator std::string() const; 41 | 42 | /*! Assignment operator to set the value of the {{Name}} instance from a ISO8601 string. 43 | * \param value the new ISO8601 string value. 44 | * \throw {{exception}} if the passed string value is not ISO8601 formatted. 45 | * \return reference to the {{Name}} instance 46 | */ 47 | {{Name}}& operator=(const std::string& value); 48 | 49 | /*! Assignment operator to set the value of the {{Name}} instance from a ISO8601 string. 50 | * \param value the new ISO8601 string value. 51 | * \throw {{exception}} the passed string value is not ISO8601 formatted. 52 | * \return reference to the {{Name}} instance 53 | */ 54 | {{Name}}& operator=(const char* value); 55 | 56 | /*! Assignment operator to set the value of the {{Name}} instance from a boost ptime. 57 | * \param value boost ptime object. 58 | * \return reference to the {{Name}} instance 59 | */ 60 | {{Name}}& operator=(const boost::posix_time::ptime& value); 61 | 62 | /*! Provide the string value of the {{Name}} instance to a stream. 63 | * \param os the stream object 64 | * \param str an instance of {{Name}} 65 | * \return the same ostream input object 66 | */ 67 | friend std::ostream& operator<<(std::ostream& os, const {{Name}}& str); 68 | 69 | /*! Set the value of the {{schema.format}}. 70 | * \param value ISO8601 {{schema.format}} representation. 71 | * \throw if the passed string isn't ISO8601 formatted. 72 | */ 73 | void Set(const std::string& value); 74 | 75 | /*! Set the value of the {{schema.format}}. 76 | * \param value ISO8601 {{schema.format}} representation. 77 | * \throw if the passed string isn't ISO8601 formatted. 78 | */ 79 | void Set(const char* value); 80 | 81 | /*! Sets the encapsulated time object. 82 | * \param datetime new time value 83 | */ 84 | void Set(const boost::posix_time::ptime& datetime); 85 | 86 | /*! Sets the encapsulated time object to represent the time this is called. 87 | */ 88 | void SetNow(); 89 | 90 | /*! Sets the encapsulated time object to always represent the current time. 91 | * If this is called, repeated calles to Get() will always return time at the moment that Get() is called. 92 | */ 93 | void SetCurrent{{schema.format | PascalCase}}(); 94 | 95 | /*! Get the ISO8601 representation of the {{schema.format}}. 96 | * \return ISO8601 representation of the {{schema.format}}. 97 | */ 98 | std::string GetString() const; 99 | 100 | /*! 101 | * Determines if calling Get() will return a fixed time or always the current time. 102 | * \return true if calling Get() will always return the current time. False indicates that calling Get() will return a static time. 103 | */ 104 | bool IsCurrentTime() const; 105 | 106 | /*! Returns boost time object. 107 | * \return The contained time, either static or dynamic current time. 108 | */ 109 | boost::posix_time::ptime Get() const; 110 | 111 | /*! Create a new {{Name}} object from a JSON string. 112 | * \throw if the JSON string wasn't ISO8601 {{schema.format}} formatted. 113 | * \return new {{Name}} object containing values from the JSON. 114 | * \param json JSON structure to use for creating new {{Name}} object. 115 | */ 116 | static {{Name}} FromJson(const rapidjson::Value& json); 117 | 118 | /*! Create a new {{Name}} object from the string. 119 | * The string must be ISO8601 {{schema.format}} formatted. 120 | * \return new {{Name}} object containing the string. 121 | * \param str the string that will be contained in the new {{Name}} object. 122 | * \throw if the string didn't meet the schema's constraints. 123 | */ 124 | static {{Name}} FromString(const std::string& str); 125 | 126 | /*! Serialize the {{Name}} instance into a JSON string. 127 | * \param value is modified to be a string type containing an ISO8601 {{schema.format}}. 128 | * \param allocator is used for rapidjson allocation. 129 | */ 130 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 131 | 132 | /*! Sets a string handle associated with this {{Name}} instance. 133 | * This gets called by a parent object after creating an instance that is used for an object's property. 134 | * \param handle is the string name. 135 | */ 136 | void SetHandle(const std::string& handle); 137 | 138 | /*! Gets the string handle associated with this {{Name}} instance. 139 | * This is often the property name used in a JSON-object parent. 140 | * It may be empty. 141 | * \returns the handle string 142 | */ 143 | std::string GetHandle() const; 144 | private: 145 | boost::optional _value; 146 | std::string _handle; 147 | }; -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/exceptions.hpp.jinja2: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | {%for n in ns-%} 10 | namespace {{n}} { 11 | {%-endfor%} 12 | 13 | /*! Encapsulates a JSON path. This is not the "JSONPath" standard, but a simple representation of a pointer to a value in a JSON-object tree. 14 | * For example, the path `A/B/C` would point to _"this is the string"_ in the following JSON object: 15 | * ``` 16 | { 17 | "A" : { 18 | "B": { 19 | "C": "this is the string", 20 | "X0": 1 21 | }, 22 | "X1": null 23 | }, 24 | "X2": false 25 | } 26 | ``` 27 | */ 28 | class JsonPath 29 | { 30 | public: 31 | /*! Empty constructor. 32 | */ 33 | JsonPath() : _pathParts({}) {} 34 | 35 | /*! Constructor with a single element in the path. 36 | * \param propName A single element in the path 37 | */ 38 | JsonPath(const std::string& propName) : _pathParts({propName}) {} 39 | 40 | /*! Copy constructor. 41 | * \param other Another JsonPath object from which to copy the path. 42 | */ 43 | JsonPath(const JsonPath& other) : _pathParts(other._pathParts) {} 44 | 45 | /*! Prepends an element to the beginning of the JSON path. 46 | * \param propName A property name to prepend to the stored path. 47 | * \returns Reference to this JsonPath object (useful for chaining commands). 48 | */ 49 | JsonPath& PrependToPath(const std::string& propName) 50 | { 51 | _pathParts.push_back(propName); 52 | return *this; 53 | } 54 | 55 | /*! 56 | * Gets a JSON path representation as strings in a vector. For example, if the path is `A/B/C` then this would return a vector with the elements `{"A", "B", "C"}`. 57 | * \returns A vector of strings representing the path. 58 | */ 59 | std::vector GetPath() const 60 | { 61 | std::vector ret; 62 | // Parts are stored in reverse order. 63 | ret.insert(ret.end(), _pathParts.crbegin(), _pathParts.crend()); 64 | return ret; 65 | } 66 | 67 | /*! 68 | * Returns a string representation of a JSON path. For example: `Highest/Middle/Leaf`. 69 | * \returns String representation of the path. 70 | */ 71 | std::string PathAsString() const 72 | { 73 | return boost::algorithm::join(GetPath(), "/"); 74 | } 75 | 76 | private: 77 | /*! In this private member variable, the parts are stored in reverse order. 78 | */ 79 | std::vector _pathParts; 80 | }; 81 | 82 | /*! This exception type should be used when there is any problem with a JSON document. 83 | */ 84 | class JsonException : public std::exception 85 | { 86 | 87 | }; 88 | 89 | /*! This exception type should be used when rapidjson couldn't parse a JSON document. 90 | */ 91 | class RapidJsonParseException : public JsonException 92 | { 93 | public: 94 | /*! Constructor (C strings). 95 | * @param whatArg C-style string error message. 96 | */ 97 | explicit RapidJsonParseException(const char* whatArg) : JsonException(), _whatArg(whatArg) { } 98 | 99 | /*! Constructor (C++ STL strings). 100 | * @param whatArg The error message. 101 | */ 102 | explicit RapidJsonParseException(const std::string& whatArg) : JsonException(), _whatArg(whatArg) { } 103 | 104 | /** Destructor. 105 | * Virtual to allow for subclassing. 106 | */ 107 | virtual ~RapidJsonParseException() throw () {} 108 | 109 | /** Returns a pointer to the (constant) error description. 110 | * @return A pointer to a const char*. 111 | */ 112 | virtual const char* what() const throw() 113 | { 114 | return _whatArg.c_str(); 115 | } 116 | 117 | protected: 118 | std::string _whatArg; 119 | }; 120 | 121 | /*! This exception type should be used for exceptions that are invalid according to a JSON schema. 122 | * It is meant to be a parent class for more specific schema exceptions to inherit from. 123 | */ 124 | class SchemaValidationException : public JsonException 125 | { 126 | 127 | }; 128 | 129 | /*! This class of exception handles any type of schema validation errors. 130 | * In additional to the error message, it also keeps the JSON path to describe the JSON-tree location of the error. 131 | */ 132 | class JsonSchemaException : public SchemaValidationException 133 | { 134 | public: 135 | 136 | /*! Simple constructor that looks like the constructor for most exceptions. 137 | * \param whatArg The message that should be include in the message returned by the `what()` method. 138 | */ 139 | JsonSchemaException(const std::string& whatArg) : SchemaValidationException(), _whatVal(""), _whatArg(whatArg), _jsonPath() 140 | { 141 | 142 | } 143 | 144 | /*! Constructor that also takes a JSON-object property name. 145 | * \param whatArg The message that should be include in the message returned by the `what()` method. 146 | * \param propName The name of the JSON-object property that failed JSON Schema validation. 147 | */ 148 | JsonSchemaException(const std::string& whatArg, const std::string& propName) : SchemaValidationException(), _whatVal(""), _whatArg(whatArg), _jsonPath(propName) 149 | { 150 | 151 | } 152 | 153 | /*! Constructor that accepts an existing JSON path. 154 | * \param whatArg The message that should be include in the message returned by the `what()` method. 155 | * \param jsonPath An existing JSON path representation object. 156 | */ 157 | JsonSchemaException(const std::string& whatArg, const JsonPath& jsonPath) : SchemaValidationException(), _whatVal(""), _whatArg(whatArg), _jsonPath(jsonPath) 158 | { 159 | 160 | } 161 | 162 | /*! Constructor that assumes the message of an existing std::exception. 163 | * \param other An existing exception from which to copy the exception message. 164 | */ 165 | explicit JsonSchemaException(const std::exception& other): SchemaValidationException(), _whatVal(""), _whatArg(other.what()), _jsonPath() 166 | { 167 | 168 | } 169 | 170 | /*! Adds to the front of the contained JSON path. 171 | * When an error is found in recursively parsing a JSON object tree, additional parts of the JSON path are prepended as the exception bubbles up through the recursive layers. 172 | * \param propName The path string to add to the front of the path. 173 | */ 174 | void PrependToPath(const std::string& propName) 175 | { 176 | _jsonPath.PrependToPath(propName); 177 | } 178 | 179 | /*! Returns the error message, prepended by a Json Path string pointing to where the exception was found. 180 | * \returns The exception's error message. 181 | */ 182 | virtual const char* what() const throw() 183 | { 184 | std::stringstream ss; 185 | ss << _jsonPath.PathAsString(); 186 | ss << ": "; 187 | ss << _whatArg; 188 | _whatVal = ss.str(); 189 | return _whatVal.c_str(); 190 | } 191 | 192 | /*! Gets the Json Path associated with the exception. 193 | * \returns The exception's Json Path. 194 | */ 195 | JsonPath WhatJsonPath() const 196 | { 197 | return _jsonPath; 198 | } 199 | 200 | /*! Gets only the exception message (without any Json path info). 201 | * \returns The exception's error string. 202 | */ 203 | std::string WhatMessage() const 204 | { 205 | return _whatArg; 206 | } 207 | 208 | private: 209 | mutable std::string _whatVal; 210 | std::string _whatArg; 211 | JsonPath _jsonPath; 212 | }; 213 | 214 | 215 | /*! This exception class can contain information from other exceptions. 216 | */ 217 | class JsonSchemaExceptionCollection : public SchemaValidationException 218 | { 219 | public: 220 | 221 | /*! Empty constructor. 222 | * This can be created before any exceptions are thrown, so that when any exceptions _are_ thrown, they can be added to this class. 223 | */ 224 | JsonSchemaExceptionCollection() : SchemaValidationException(), _whatVal(""), _subExceptions({}) { } 225 | 226 | /*! Default destructor. 227 | */ 228 | virtual ~JsonSchemaExceptionCollection() = default; 229 | 230 | /*! Adds an exception to the collection. 231 | * \param exc The exception to add to the collection. 232 | */ 233 | void AddException(const JsonSchemaException& exc) 234 | { 235 | _subExceptions.push_back(exc); 236 | } 237 | 238 | /*! Adds an exception to the collection, setting the Json path associated with the exception. 239 | * \param exc The exception to add to the collection. 240 | * \param propName This is the name of the property from which the exception was thrown. 241 | */ 242 | void AddException(const JsonSchemaException& exc, const std::string& propName) 243 | { 244 | _subExceptions.push_back(exc); 245 | _subExceptions.back().PrependToPath(propName); 246 | } 247 | 248 | /*! Adds all the exceptions from a different JsonSchemaExceptionCollection to this collection. 249 | * \param exc A different exception collection from which to copy exceptions. 250 | */ 251 | void AddException(const JsonSchemaExceptionCollection& exc) 252 | { 253 | auto exceptions = exc.GetExceptions(); 254 | _subExceptions.insert(_subExceptions.end(), exceptions.begin(), exceptions.end()); 255 | } 256 | 257 | /*! Adds all the exceptions from a different JsonSchemaExceptionCollection to this collection. 258 | * \param exc A different exception collection from which to copy exceptions. 259 | * \param propName This is the name of the property from which the exception was thrown 260 | */ 261 | void AddException(const JsonSchemaExceptionCollection& exc, const std::string& propName) 262 | { 263 | std::vector exceptions = exc.GetExceptions(); 264 | for (JsonSchemaException iter : exceptions) 265 | { 266 | iter.PrependToPath(propName); 267 | _subExceptions.push_back(iter); 268 | } 269 | } 270 | 271 | /*! Adds any exception to the collection. 272 | * \param exc The exception to add to the collection. 273 | */ 274 | void AddException(const std::exception& exc) 275 | { 276 | _subExceptions.push_back(JsonSchemaException(exc)); 277 | } 278 | 279 | /*! Adds any exception to the collection. 280 | * \param exc The exception to add to the collection. 281 | * \param propName This is the name of the property from which the exception was thrown. 282 | */ 283 | void AddException(const std::exception& exc, const std::string& propName) 284 | { 285 | _subExceptions.push_back(JsonSchemaException(exc)); 286 | } 287 | 288 | /*! Prepends an additional layer to the JSON path of each contained object. 289 | * When a JSON object is being recursively parsed, this is needed to add to the JSON paths as the exceptions bubble upward. 290 | * \param propName This name will get prepended to each contained exception's JSON path. 291 | */ 292 | void PrependToPath(const std::string& propName) 293 | { 294 | for (auto& iter : _subExceptions) 295 | { 296 | iter.PrependToPath(propName); 297 | } 298 | } 299 | 300 | /*! Indicates if there are any collection exceptions. 301 | * \return true if one or more exceptions have been collected. 302 | */ 303 | bool IsExceptional() const { 304 | return !_subExceptions.empty(); 305 | } 306 | 307 | /*! Returns the error message. 308 | * \returns The exception's error message. 309 | */ 310 | virtual const char* what() const throw() 311 | { 312 | if (!IsExceptional()) 313 | { 314 | return ""; 315 | } 316 | _whatVal = ""; 317 | for (auto& iter : _subExceptions) 318 | { 319 | if (!_whatVal.empty()) 320 | { 321 | _whatVal += " ; "; 322 | } 323 | _whatVal += iter.what(); 324 | } 325 | return _whatVal.c_str(); 326 | } 327 | 328 | /*! Get the collection of exceptions. 329 | * \returns Collection of exceptions. 330 | */ 331 | std::vector GetExceptions() const 332 | { 333 | return _subExceptions; 334 | } 335 | 336 | private: 337 | mutable std::string _whatVal; 338 | std::vector _subExceptions; 339 | }; 340 | 341 | {%for n in ns-%} 342 | } //end namespace {{n}} 343 | {%-endfor%} 344 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/header.hpp.jinja2: -------------------------------------------------------------------------------- 1 | // This file is automatically generated using a JSON-Schema definition. 2 | // DO NOT MODIFY this file, as changes will be overwritten on the next code 3 | // generation. 4 | 5 | #pragma once 6 | 7 | #include 8 | {%-for dep in deps|sort|reverse %} 9 | {%-if 'rapidjson' not in dep %}{# rapidjson should go last and always #} 10 | #include {{dep}} 11 | {%-endif-%} 12 | {%-endfor%} 13 | #include "{{resolver.cpp_get_lib_ns() | join("/")}}{%if resolver.cpp_get_lib_ns() %}/{%endif%}exceptions.hpp" 14 | 15 | // FIXME: Somehow rapidjson provides a copy of inttypes.h that conflicts 16 | // with what google/breakpad needs. This flag gets things to compile, but 17 | // we probably need to look into the issue further, especially if we 18 | // ever try to use 64-bit ints. 19 | #define RAPIDJSON_NO_INT64DEFINE 20 | #include "rapidjson/document.h" 21 | 22 | {%for n in ns %} 23 | namespace {{n}} { 24 | {%-endfor%} 25 | 26 | {%from 'loader.jinja2' import Class with context%} 27 | {{Class('hpp', resolver, ns, Name, schema)}} 28 | 29 | {%-for n in ns|reverse %} 30 | } // end ns {{n}} 31 | {%-endfor%} 32 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/loader.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro Class(filetype, resolver, ns, Name, schema) %} 2 | {%-if schema.oneOf is defined -%} 3 | {%-include 'oneof.'+filetype+'.jinja2'-%} 4 | {%-elif schema.allOf is defined -%} 5 | {%-include 'allof.'+filetype+'.jinja2'-%} 6 | {%-elif schema.anyOf is defined -%} 7 | {%-include 'anyof.'+filetype+'.jinja2'-%} 8 | {%-elif '$ref' in schema -%} 9 | // An object representing {{schema['$ref']}} is used in this structure 10 | {# -#} 11 | {%-if filetype == 'hpp' -%} 12 | {%-include 'ref.hpp.jinja2'-%} 13 | {%-endif-%} 14 | {%-elif 'type' not in schema-%} 15 | {%-elif schema.type == 'integer' or schema.type == 'number'-%} 16 | {%-include 'numeric.'+filetype+'.jinja2'-%} 17 | {%-elif schema.type == 'array'-%} 18 | {%-include 'array.'+filetype+'.jinja2'-%} 19 | {%-elif schema.type == 'object'-%} 20 | {%-include 'object.'+filetype+'.jinja2'-%} 21 | {%-elif schema.type == 'boolean'-%} 22 | {%-include 'bool.'+filetype+'.jinja2'-%} 23 | {%-elif schema.type == 'null'-%} 24 | {%-include 'null.'+filetype+'.jinja2'-%} 25 | {%-elif schema.type == 'string' and schema.enum is not defined-%} 26 | {%-if schema.format == 'date-time'-%} 27 | {%-include 'datetime.'+filetype+'.jinja2'-%} 28 | {%-else-%} 29 | {%-include 'string.'+filetype+'.jinja2'-%} 30 | {%-endif-%} 31 | {%-elif schema.type == 'string' and schema.enum is defined-%} 32 | {%-include 'string_enum.'+filetype+'.jinja2'-%} 33 | {%-else-%} 34 | // FIXME: Failed at attempt to generate object for schema of type {{schema.type-}} 35 | {%-endif-%} 36 | {%-endmacro-%} 37 | {%-macro Reference(resolver, ref) %} 38 | {{-resolver.cpp_get_ns_name(ref)-}} 39 | {%-endmacro-%} 40 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/null.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 2 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 3 | {{className}}::{{Name}}() 4 | { 5 | } 6 | 7 | boost::none_t {{className}}::Get() const 8 | { 9 | return boost::none; 10 | } 11 | 12 | {{className}} {{className}}::FromJson(const {{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& json) 13 | { 14 | if (!(json.IsNull())) 15 | { 16 | throw {{exception}}("Not the NULL value"); 17 | } 18 | 19 | return {{className}}(); 20 | } 21 | 22 | void {{className}}::ToJson({{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 23 | { 24 | value.SetNull(); 25 | } 26 | 27 | void {{className}}::SetHandle(const std::string& handle) 28 | { 29 | _handle = handle; 30 | } 31 | 32 | std::string {{className}}::GetHandle() const 33 | { 34 | return _handle; 35 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/null.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 2 | /*! {{Name}} is a wrapper around "null". 3 | {%-if schema.description %} 4 | * {{schema.description}} 5 | {%-endif%} 6 | */ 7 | class {{Name}} 8 | { 9 | public: 10 | /*! Constructor 11 | */ 12 | {{Name}}(); 13 | 14 | virtual ~{{Name}}() = default; 15 | 16 | /*! \fn boost::none_t Get() const 17 | * \brief returns boost::none which represents "null" 18 | * This is pointless because it never returns anything other than boost::none 19 | * \returns boost::none 20 | */ 21 | boost::none_t Get() const; 22 | 23 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 24 | * \brief Deserializes a JSON "null" value into a new instance of {{Name}}. 25 | * \param json is the RapidJSON value which must be of "null" type. 26 | * \throw {{exception}} If the JSON isn't a null 27 | * \returns {{Name}} 28 | */ 29 | static {{Name}} FromJson(const rapidjson::Value& json); 30 | 31 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 32 | * \brief Sets 'value' to null 33 | * \param value is the RapidJSON value which will be modified to contain the serialization 34 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 35 | */ 36 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 37 | 38 | /*! Sets a string handle associated with this {{Name}} instance. 39 | * This gets called by a parent object after creating an instance that is used for an object's property. 40 | * \param handle is the string name. 41 | */ 42 | void SetHandle(const std::string& handle); 43 | 44 | /*! Gets the string handle associated with this {{Name}} instance. 45 | * This is often the property name used in a JSON-object parent. 46 | * It may be empty. 47 | * \returns the handle string 48 | */ 49 | std::string GetHandle() const; 50 | private: 51 | std::string _handle; 52 | }; -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/numeric.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'constraints.jinja2' as constraint-%} 2 | {%-if schema.type == 'integer'%}{%-set rjtype = 'int'%}{%-else%}{%-set rjtype = 'number'%}{%-endif-%} 3 | {%-if schema.type == 'integer'%}{%-set cpptype = 'int'%}{%-else%}{%-set cpptype = 'double'%}{%-endif%} 4 | {%-set std = resolver.cpp_resolve_namespace(['std']) %} 5 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 6 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 7 | {%if schema.const is not defined%} 8 | {{className}}::{{Name}}({{cpptype}} value) 9 | { 10 | Set(value); 11 | } 12 | {%endif%} 13 | {%-if schema.default is defined or schema.const is defined%} 14 | {%set emptyConstructor = true %} 15 | {{className}}::{{Name}}() 16 | { 17 | Set({%-if schema.const is defined-%}{{constraint.ExprName('const')}}{%-else-%}{{constraint.ExprName('default')}}{%-endif-%}); 18 | } 19 | {%-endif%} 20 | 21 | {{className}}::operator {{cpptype}}() const 22 | { 23 | return Get(); 24 | } 25 | 26 | {{className}}& {{className}}::operator=({{cpptype}} value) 27 | { 28 | Set(value); 29 | return *this; 30 | } 31 | {%for origNs in originalNamespace %} 32 | namespace {{origNs}} { 33 | {%-endfor%} 34 | {{std}}ostream& operator<<({{std}}ostream& os, const {{className}}& num) 35 | { 36 | os << num._value; 37 | return os; 38 | } 39 | 40 | bool operator< (const {{className}}& left, const {{className}}& right) 41 | { 42 | return left._value < right._value; 43 | } 44 | 45 | {{std}}size_t hash_value(const {{className}}& num) 46 | { 47 | return boost::hash_value(num._value); 48 | } 49 | {%for origNs in originalNamespace %}}{%-endfor%} // end namespaces 50 | 51 | {{className}}& {{className}}::Set({{cpptype}} value) 52 | { 53 | Validate(value); 54 | _value = value; 55 | return *this; 56 | } 57 | 58 | {{cpptype}} {{className}}::Get() const 59 | { 60 | return _value; 61 | } 62 | 63 | void {{className}}::Validate({{cpptype}} testValue) 64 | { 65 | {%-if schema.minimum is defined %} 66 | if (testValue < {{constraint.ExprName('minimum')}}) throw {{exception}}("Value was less than {{schema.minimum}}"); 67 | {%-endif%} 68 | {%-if schema.exclusiveMinimum is defined %} 69 | if (testValue <= {{constraint.ExprName('exclusiveMinimum')}}) throw {{exception}}("Value was less than or equal to {{schema.exclusiveMinimum}}"); 70 | {%-endif%} 71 | {%-if schema.maximum is defined %} 72 | if (testValue > {{constraint.ExprName('maximum')}}) throw {{exception}}("Value was more than {{schema.maximum}}"); 73 | {%-endif%} 74 | {%-if schema.exclusiveMaximum is defined %} 75 | if (testValue >= {{constraint.ExprName('exclusiveMaximum')}}) throw {{exception}}("Value was less than or equal to {{schema.exclusiveMaximum}}"); 76 | {%-endif%} 77 | {%-if schema.multipleOf is defined %} 78 | if ((testValue % {{constraint.ExprName('multipleOf')}}) != 0) throw {{exception}}("Value was less than or equal to {{schema.exclusiveMaximum}}"); 79 | {%-endif%} 80 | {%-if schema.const is defined%} 81 | if (testValue != {{constraint.ExprName('const')}}) throw {{exception}}("Value was not {{schema.const}}"); 82 | {%-endif%} 83 | } 84 | 85 | {{className}} {{className}}::FromJson(const {{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& json) 86 | { 87 | if (!(json.Is{{rjtype | UpperCamelCase}}())) 88 | { 89 | throw {{exception}}("Wasn't a {{rjtype}}"); 90 | } 91 | 92 | {{className}} newObject(json.Get{{cpptype | UpperCamelCase}}()); 93 | return newObject; 94 | } 95 | 96 | {{className}} {{className}}::FromString(const {{std}}string& str) 97 | { 98 | return {{className}}({{resolver.cpp_resolve_namespace(['boost'])}}lexical_cast<{{cpptype}}>(str)); 99 | } 100 | 101 | void {{className}}::ToJson({{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 102 | { 103 | value.Set{{cpptype | UpperCamelCase}}(_value); 104 | } 105 | 106 | void {{className}}::SetHandle(const std::string& handle) 107 | { 108 | _handle = handle; 109 | } 110 | 111 | std::string {{className}}::GetHandle() const 112 | { 113 | return _handle; 114 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/numeric.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'constraints.jinja2' as constraint-%} 2 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 3 | {%-if schema.type == 'integer'%}{%-set cpptype = 'int'%}{%-else%}{%-set cpptype = 'double'%}{%-endif-%} 4 | /*! {{Name}} is a wrapper around a {{schema.type}}. 5 | * \brief wrapper around {{cpptype}} 6 | {%-if schema.description %} 7 | * {{schema.description}} 8 | {%-endif%} 9 | */ 10 | class {{Name}} 11 | { 12 | public: 13 | {%-if schema.minimum is defined %} 14 | /*! The encapsulated value must not be less than this minimum number. 15 | * This is defined as a static value to encourage static assertations in application code. 16 | */ 17 | static constexpr int {{constraint.ExprName('minimum')}} = {{schema.minimum}}; 18 | {%endif%} 19 | {%-if schema.maximum is defined %} 20 | /*! The encapsulated value must not exceed this maximum number. 21 | * This is defined as a static value to encourage static assertations in application code. 22 | */ 23 | static constexpr int {{constraint.ExprName('maximum')}} = {{schema.maximum}}; 24 | {%endif%} 25 | {%-if schema.exclusiveMinimum is defined %} 26 | /*! The encapsulated value must be greater than this minimum number. 27 | * This is defined as a static value to encourage static assertations in application code. 28 | */ 29 | static constexpr int {{constraint.ExprName('exclusiveMinimum')}} = {{schema.exclusiveMinimum}}; 30 | {%endif%} 31 | {%-if schema.exclusiveMaximum is defined %} 32 | /*! The encapsulated value must be less than this maximum number. 33 | * This is defined as a static value to encourage static assertations in application code. 34 | */ 35 | static constexpr int {{constraint.ExprName('exclusiveMaximum')}} = {{schema.exclusiveMaximum}}; 36 | {%endif%} 37 | {%-if schema.multipleOf is defined %} 38 | /*! The encapsulated value must be a multiple of {{schema.multipleOf}}. 39 | * This is defined as a static value to encourage static assertations in application code. 40 | */ 41 | static constexpr int {{constraint.ExprName('multipleOf')}} = {{schema.multipleOf}}; 42 | {%endif%} 43 | {%-if schema.default is defined %} 44 | /*! This is the default value encapsulated by {{Name}}. 45 | * This is defined as a static value to encourage static assertations in application code. 46 | */ 47 | static constexpr int {{constraint.ExprName('default')}} = {{schema.default}}; 48 | {%endif%} 49 | {%-if schema.const is defined %} 50 | /*! The encapsulated value must only be this value: {{schema.const}}. 51 | * This is defined as a static value to encourage static assertations in application code. 52 | */ 53 | static constexpr int {{constraint.ExprName('const')}} = {{schema.const}}; 54 | {%endif%} 55 | {%-if schema.const is not defined%} 56 | /*! Constructor 57 | * \param value the initial value 58 | */ 59 | {{Name}}({{cpptype}} value); 60 | {%endif%} 61 | {%-if schema.default is defined or schema.const is defined %} 62 | /*! Default value constructor. 63 | * This structure is initialized with the value '{{schema.const or schema.default}}' 64 | */ 65 | {{Name}}(); 66 | {%-endif%} 67 | 68 | virtual ~{{Name}}() = default; 69 | 70 | /*! Cast to {{cpptype}} operator 71 | */ 72 | operator {{cpptype}}() const; 73 | 74 | /*! Stream operator. 75 | */ 76 | friend std::ostream& operator<<(std::ostream& os, const {{Name}}& num); 77 | 78 | /*! The less than comparison operator. 79 | * \param left A {{Name}} instance on the left size of the less than expression. 80 | * \param right A {{Name}} instance on the right side of the less than expression. 81 | * \return true if the less than expression is true. 82 | */ 83 | friend bool operator< (const {{Name}}& left, const {{Name}}& right); 84 | 85 | /*! Produce a hash value of the object. 86 | * \param num A {{Name}} instance from which to produce a hash. 87 | * \return a hash number using a boost hashing algorithm. 88 | */ 89 | friend std::size_t hash_value(const {{Name}}& num); 90 | 91 | /*! Gets the value. 92 | * \fn {{cpptype}} Get() const 93 | * \return {{cpptype}} value 94 | */ 95 | {{cpptype}} Get() const; 96 | 97 | /*! Validates that a value meets schema requirements. 98 | * This is used internally anytime a value is passed into the class, including from the constructor. 99 | * \param testValue This value is checked against all schema contraints. 100 | * \throw {{exception}} When the provided value is invalid according to the schema. 101 | */ 102 | static void Validate({{cpptype}} testValue); 103 | 104 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 105 | * \brief Deserializes a JSON "{{schema.type}}" value into a new instance of {{Name}}. 106 | * \param json is the RapidJSON value which must be of "null" type. 107 | * \throw {{exception}} If the JSON isn't a {{schema.type}} 108 | * \throw {{exception}} If the value doesn't meet schema constraints 109 | * \returns {{Name}} 110 | */ 111 | static {{Name}} FromJson(const rapidjson::Value& json); 112 | 113 | /*! \fn {{Name}} FromString(const std::string& str) 114 | * \brief Performs a "lexical cast" on the string to create a new {{Name}} object 115 | * \param str stringified representation of a {{schema.type}} 116 | * \throw {{exception}} If the string cannot be cast or does not JSON-schema validate 117 | * \return {{Name}} 118 | */ 119 | static {{Name}} FromString(const std::string& str); 120 | 121 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 122 | * \brief Sets 'value' to a {{schema.type}} 123 | * \param value is the RapidJSON value which will be modified to contain the serialization 124 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 125 | */ 126 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 127 | 128 | /*! Sets a string handle associated with this {{Name}} instance. 129 | * This gets called by a parent object after creating an instance that is used for an object's property. 130 | * \param handle is the string name. 131 | */ 132 | void SetHandle(const std::string& handle); 133 | 134 | /*! Gets the string handle associated with this {{Name}} instance. 135 | * This is often the property name used in a JSON-object parent. 136 | * It may be empty. 137 | * \returns the handle string 138 | */ 139 | std::string GetHandle() const; 140 | 141 | {%if schema.IsReadOnly() or schema.const is defined%} 142 | private: 143 | {%-endif%} 144 | /*! Assignment from {{cpptype}} operator 145 | */ 146 | {{Name}}& operator=({{cpptype}} value); 147 | 148 | /*! Sets the {{cpptype}} value. 149 | * \fn void Set({{cpptype}} value) 150 | * \param value new value 151 | */ 152 | {{Name}}& Set({{cpptype}} value); 153 | private: 154 | {{cpptype}} _value; 155 | std::string _handle; 156 | }; -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/object.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'propname.jinja2' as helper-%} 2 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 3 | {%-set rapidjson = resolver.cpp_resolve_namespace(['rapidjson']) %} 4 | {%macro NestedObjectNameOnly(name) -%} 5 | {%-if name|UpperCamelCase != Name|UpperCamelCase%}{{-name | UpperCamelCase-}}{%else%}{{name | UpperCamelCase}}Property{%endif-%} 6 | {%-endmacro%} 7 | {%macro NestedObjectName(name) -%} 8 | {{className}}::{{NestedObjectNameOnly(name)}} 9 | {%-endmacro%} 10 | {%-macro ObjectType(propName, propSchema) -%} 11 | {%-if '$ref' in propSchema -%} 12 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 13 | {%-else-%} 14 | {{-NestedObjectName(propName)-}} 15 | {%-endif-%} 16 | {%-endmacro%} 17 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset-%} 18 | 19 | {%import 'loader.jinja2' as loader with context%} 20 | {%-for propName, propSchema in schema.RequiredList() %} 21 | {{loader.Class('cpp', resolver, [className], NestedObjectNameOnly(propName), propSchema) }} 22 | {%-endfor%} 23 | {%-for propName, propSchema in schema.UnRequiredList() %} 24 | {{loader.Class('cpp', resolver, [className], NestedObjectNameOnly(propName), propSchema) }} 25 | {%-endfor%} 26 | 27 | {%if schema.RequiredList() | length > 0 %} 28 | {{className}}::{{Name}}( 29 | {%-for propName, propSchema in schema.RequiredList()-%} 30 | const {{ObjectType(propName, propSchema)}}& {{propName | camelCase}}{%if not loop.last%}, {%endif%} 31 | {%-endfor%}){{' : '}} 32 | {%-for propName, propSchema in schema.RequiredList()-%} 33 | {{propName|privatize}}({{propName | camelCase}}){%if not loop.last%}, {%endif%} 34 | {%-endfor%} 35 | { 36 | 37 | } 38 | {%else%} 39 | {{className}}::{{Name}}() 40 | { 41 | 42 | } 43 | 44 | {%-if schema['properties'] | length == 1 %} 45 | {%-set propName = schema.PropertyKeys()[0] %} 46 | {%-set propSchema = schema.PropertyValues()[0] %} 47 | {{className}}::{{Name}}(const {{ObjectType(propName, propSchema)}}& {{propName | camelCase}}) : {{propName|privatize}}({{propName | camelCase}}) 48 | { 49 | 50 | } 51 | {%-endif%} 52 | {%if 'type' in propSchema and propSchema['type'] in ['boolean', 'integer', 'number'] %} 53 | {{className}}::{{Name}}({{ {"boolean":"bool", "integer":"int", "number":"double"}[propSchema.type] }} {{propName | camelCase}}) : {{Name}}({{ObjectType(propName, propSchema)}}({{propName | camelCase}})) 54 | { 55 | 56 | } 57 | {%-elif 'type' in propSchema and propSchema['type'] == 'string'-%} 58 | {{className}}::{{Name}}(const std::string& {{propName | camelCase}}) : {{Name}}({{ObjectType(propName, propSchema)}}::FromString({{propName | camelCase}})) 59 | { 60 | 61 | } 62 | {%-endif%} {# propschema type #} 63 | {%endif%} 64 | 65 | {%-for propName, propSchema in schema.properties.items() %} 66 | {%-if propName in schema.required%} 67 | 68 | {{ObjectType(propName, propSchema)}} {{className}}::Get{{propName | UpperCamelCase}}() const 69 | { 70 | return {{propName | privatize}}; 71 | } 72 | 73 | {{className}}& {{className}}::Set{{propName | UpperCamelCase}}(const {{ObjectType(propName, propSchema)}}& value) 74 | { 75 | {{propName | privatize}} = value; 76 | {{propName | privatize}}.SetHandle({{helper.ConstPropertyName(propName)}}); 77 | return *this; 78 | } 79 | {%else%} 80 | boost::optional<{{ObjectType(propName, propSchema)}}> {{className}}::Get{{propName | UpperCamelCase}}() const 81 | { 82 | return {{propName | privatize}}; 83 | } 84 | 85 | {{className}}& {{className}}::Set{{propName | UpperCamelCase}}(const {{ObjectType(propName, propSchema)}}& value) 86 | { 87 | {{propName | privatize}} = value; 88 | {{propName | privatize}}->SetHandle({{helper.ConstPropertyName(propName)}}); 89 | return *this; 90 | } 91 | {%endif%} 92 | {%-endfor%} 93 | 94 | {{className}} {{className}}::FromJson(const {{rapidjson}}Value& json) 95 | { 96 | if (!(json.IsObject())) 97 | { 98 | throw {{exception}}("JSON wasn't an object"); 99 | } 100 | {##} 101 | {{exception}}Collection exceptionCollection; 102 | {%for propName, propSchema in schema.RequiredList()-%} 103 | boost::optional<{{ObjectType(propName, propSchema)}}> optLocal{{propName | UpperCamelCase}}; 104 | if (!json.HasMember({{helper.ConstPropertyName(propName)}})) 105 | { 106 | exceptionCollection.AddException({{exception}}("Property is missing", "{{propName}}")); 107 | } 108 | else 109 | { 110 | optLocal{{propName | UpperCamelCase}} = {{ObjectType(propName, propSchema)}}::FromJson(json[{{helper.ConstPropertyName(propName)}}]); 111 | optLocal{{propName | UpperCamelCase}}->SetHandle({{helper.ConstPropertyName(propName)}}); 112 | } 113 | {%endfor%} 114 | boost::optional<{{className}}> optNewInstance; 115 | {%-if schema.RequiredList() %} 116 | if ({%for propName, propSchema in schema.RequiredList()%}optLocal{{propName | UpperCamelCase}}{%if not loop.last%} && {%endif%}{%endfor%}) 117 | { 118 | optNewInstance = {{className}}({%for propName, propSchema in schema.RequiredList()%}*optLocal{{propName | UpperCamelCase}}{%if not loop.last%}, {%endif%}{%endfor%}); 119 | } 120 | {%else%} 121 | // Yes I know this looks weird to create a boost::optional only to immediately instantiate it. 122 | // However, this is generated code, and there is a branch you don't see here where we conditionally instantiate it. 123 | optNewInstance = {{className}}(); 124 | {%-endif%} 125 | {%-for propName, propSchema in schema.UnRequiredList()%} 126 | if (json.HasMember({{helper.ConstPropertyName(propName)}})) 127 | { 128 | try 129 | { 130 | auto local{{propName | UpperCamelCase}} = {{ObjectType(propName, propSchema)}}::FromJson(json[{{helper.ConstPropertyName(propName)}}]); 131 | local{{propName | UpperCamelCase}}.SetHandle({{helper.ConstPropertyName(propName)}}); 132 | if (optNewInstance) 133 | { 134 | optNewInstance->Set{{propName | UpperCamelCase}}(local{{propName | UpperCamelCase}}); 135 | } 136 | } 137 | catch (const {{exception}}& e) 138 | { 139 | exceptionCollection.AddException(e, {{helper.ConstPropertyName(propName)}}); 140 | } 141 | catch (const {{exception}}Collection& ec) 142 | { 143 | exceptionCollection.AddException(ec, {{helper.ConstPropertyName(propName)}}); 144 | } 145 | catch (const std::exception& e) 146 | { 147 | exceptionCollection.AddException({{exception}}(e), {{helper.ConstPropertyName(propName)}}); 148 | } 149 | } 150 | {%-endfor%} 151 | if (exceptionCollection.IsExceptional() || !optNewInstance) 152 | { 153 | throw exceptionCollection; 154 | } 155 | return *optNewInstance; 156 | } 157 | 158 | void {{className}}::ToJson({{rapidjson}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 159 | { 160 | if (!value.IsObject()) 161 | { 162 | value.SetObject(); 163 | } 164 | 165 | {%-for propName, propSchema in schema.properties.items() %} 166 | {%-if propName in schema.required%} 167 | {{rapidjson}}Value temp{{propName|UpperCamelCase}}; 168 | {{propName|privatize}}.ToJson(temp{{propName|UpperCamelCase}}, allocator); 169 | value.AddMember({{rapidjson}}StringRef({{helper.ConstPropertyName(propName)}}), temp{{propName|UpperCamelCase}}, allocator); 170 | {%else%} 171 | if ({{propName|privatize}}) 172 | { 173 | {{rapidjson}}Value temp{{propName|UpperCamelCase}}; 174 | {{propName|privatize}}->ToJson(temp{{propName|UpperCamelCase}}, allocator); 175 | value.AddMember({{rapidjson}}StringRef({{helper.ConstPropertyName(propName)}}), temp{{propName|UpperCamelCase}}, allocator); 176 | } 177 | {%endif%} 178 | {%-endfor%} 179 | } 180 | 181 | void {{className}}::SetHandle(const std::string& handle) 182 | { 183 | _handle = handle; 184 | } 185 | 186 | std::string {{className}}::GetHandle() const 187 | { 188 | return _handle; 189 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/object.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader-%} 2 | {%-import 'propname.jinja2' as helper-%} 3 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset-%} 4 | {%macro NestedObjectName(name) -%} 5 | {%-if name|UpperCamelCase != Name|UpperCamelCase%}{{-name | UpperCamelCase-}}{%else%}{{name | UpperCamelCase}}Property{%endif-%} 6 | {%-endmacro%} 7 | {%-macro ObjectType(propName, propSchema) -%} 8 | {%-if '$ref' in propSchema -%} 9 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 10 | {%-else-%} 11 | {{-NestedObjectName(propName)-}} 12 | {%-endif-%} 13 | {%-endmacro%} 14 | /*! {{Name}} is a wrapper around an object. 15 | * \brief Wrapper around object with properties: {%for propName in schema.properties.keys() %}{{propName}}{%if not loop.last%}, {%endif%}{%endfor%} 16 | {%-if schema.description %} 17 | * {{schema.description}} 18 | {%-endif%} 19 | */ 20 | class {{Name}} 21 | { 22 | public: 23 | {%-for propName, propSchema in schema.properties.items() %} 24 | /*! The key name used in the exported JSON object. 25 | */ 26 | static constexpr auto {{helper.ConstPropertyName(propName)}} = "{{propName}}"; 27 | {%endfor-%} 28 | {%-for propName, propSchema in schema.properties.items() %} 29 | {%-if '$ref' not in propSchema %} 30 | {{loader.Class('hpp', resolver, [Name], NestedObjectName(propName), propSchema) | indent(4) }} 31 | {%-endif%} 32 | {%endfor%} 33 | 34 | {%-if schema.RequiredList() | length > 0 %} 35 | /*! Constructor that sets required properties. 36 | */ 37 | {{Name}}({%for propName, propSchema in schema.RequiredList()%}const {{ObjectType(propName, propSchema)}}& {{propName | camelCase}}{%if not loop.last%}, {%endif%}{%endfor%}); 38 | {%-else%} 39 | /*! Constructor that initializes all properties to their defaults. 40 | */ 41 | {{Name}}(); 42 | {%if schema['properties'] | length == 1 %} 43 | {%-set propName = schema.PropertyKeys()[0] %} 44 | {%-set propSchema = schema.PropertyValues()[0] %} 45 | /*! Constructor for initializing object's single property. 46 | * \param {{propName | camelCase}} property to initialize. 47 | */ 48 | {{Name}}(const {{ObjectType(propName, propSchema)}}& {{propName | camelCase}}); 49 | {%if 'type' in propSchema and propSchema['type'] in ['boolean', 'integer', 'string', 'number'] %} 50 | /*! Constructor for initializing object's single {{propSchema.type}} property. 51 | * \param {{propName | camelCase}} value for initialization. 52 | */ 53 | {{Name}}({{ {"boolean":"bool", "integer":"int", "string":"const std::string&", "number":"double"}[propSchema.type] }} {{propName | camelCase}}); 54 | {%-endif%} {# propschema type #} 55 | {%-endif%} {# single property object #} 56 | 57 | {%-endif%} 58 | 59 | /*! Destructor. 60 | */ 61 | virtual ~{{Name}}() = default; 62 | {%for propName, propSchema in schema.properties.items() %} 63 | {%-if propName in schema.required%} 64 | /*! Returns the required '{{propName}}' property of the object 65 | * \fn {{ObjectType(propName, propSchema)}} Get{{propName | UpperCamelCase}}() const 66 | * \returns {{ObjectType(propName, propSchema)}} Required property value 67 | */ 68 | {{ObjectType(propName, propSchema)}} Get{{propName | UpperCamelCase}}() const; 69 | {%else%} 70 | /*! Returns the optional '{{propName}}' property of the object 71 | * \fn boost::optional<{{ObjectType(propName, propSchema)}}> Get{{propName | UpperCamelCase}}() const 72 | * \returns boost::optional<{{ObjectType(propName, propSchema)}}> Optional property value 73 | */ 74 | boost::optional<{{ObjectType(propName, propSchema)}}> Get{{propName | UpperCamelCase}}() const; 75 | {%-endif%} 76 | 77 | /*! Sets the '{{propName}}' property of the object. 78 | * \fn void Set{{propName | UpperCamelCase}}(const {{ObjectType(propName, propSchema)}}& value) 79 | * \param value the value which is set as the {{propName}} property. 80 | */ 81 | {{Name}}& Set{{propName | UpperCamelCase}}(const {{ObjectType(propName, propSchema)}}& value); 82 | {%-endfor%} 83 | 84 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 85 | * \brief Deserializes a JSON "object" value into a new instance of {{Name}}. 86 | * \param json is the RapidJSON value which must be of "object" type. 87 | * \throw {{exception}} If the JSON value wasn't an object. 88 | * \throw {{exception}}Collection if any property was missing or didn't validate. 89 | * \returns {{Name}} 90 | */ 91 | static {{Name}} FromJson(const rapidjson::Value& json); 92 | 93 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 94 | * \brief Sets 'value' to a JSON object 95 | * \param value is the RapidJSON value which will be modified to contain the serialization 96 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 97 | */ 98 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 99 | 100 | /*! Sets a string handle associated with this {{Name}} instance. 101 | * This gets called by a parent object after creating an instance that is used for an object's property. 102 | * \param handle is the string name. 103 | */ 104 | void SetHandle(const std::string& handle); 105 | 106 | /*! Gets the string handle associated with this {{Name}} instance. 107 | * This is often the property name used in a JSON-object parent. 108 | * It may be empty. 109 | * \returns the handle string 110 | */ 111 | std::string GetHandle() const; 112 | private: 113 | {%-for propName, propSchema in schema.properties.items() %} 114 | {%-if propName in schema.required %} 115 | {{ObjectType(propName, propSchema)}} {{propName | privatize}}; 116 | {%-else%} 117 | boost::optional<{{ObjectType(propName, propSchema)}}> {{propName | privatize}}; 118 | {%-endif%} 119 | {%-endfor%} 120 | std::string _handle; 121 | }; 122 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/oneof.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader with context-%} 2 | {%-set std = resolver.cpp_resolve_namespace(['std']) %} 3 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 4 | {%macro NestedObjectName(name) -%} 5 | {{className}}::{{-name | UpperCamelCase-}} 6 | {%-endmacro%} 7 | {%-macro ObjectType(propName, propSchema) -%} 8 | {%-if '$ref' in propSchema -%} 9 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 10 | {%-else-%} 11 | {{-NestedObjectName(propName)-}} 12 | {%-endif-%} 13 | {%-endmacro%} 14 | {%-set optionList -%} 15 | {%-for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%} 16 | {{ObjectType(optionName, s)}}{%if not loop.last%}, {%endif-%} 17 | {%-endfor -%} 18 | {%-endset%} 19 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 20 | 21 | {%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%} 22 | {{loader.Class('cpp', resolver, [className], optionName, s) }} 23 | {%-endfor%} 24 | 25 | {%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%} 26 | {{className}}::{{Name}}(const {{ObjectType(optionName, s)}}& obj) : _value(obj) 27 | { 28 | 29 | } 30 | {%endfor%} 31 | 32 | {%set commonType = schema.GetCommonType(resolver)%} 33 | {%-if commonType in ['boolean', 'integer', 'number', string] %} 34 | {%for origNs in originalNamespace %} 35 | namespace {{origNs}} { 36 | {%-endfor%} 37 | {{std}}ostream& operator<<({{std}}ostream& os, const {{className}}& inst) 38 | { 39 | {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%} 40 | {%if not loop.first%} 41 | else {%endif%}if (inst._value.which() == {{loop.index - 1}}) 42 | { 43 | os << boost::get<{{ObjectType(optionName, s)}}>(inst._value); 44 | } 45 | {%-endfor %} 46 | return os; 47 | } 48 | 49 | {{std}}size_t hash_value(const {{className}}& inst) 50 | { 51 | {{std}}size_t seed = 0; 52 | {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%} 53 | {%if not loop.first%} 54 | else {%endif%}if (inst._value.which() == {{loop.index - 1}}) 55 | { 56 | boost::hash_combine(seed, boost::get<{{ObjectType(optionName, s)}}>(inst._value)); 57 | } 58 | {%-endfor %} 59 | return seed; 60 | } 61 | {%for origNs in originalNamespace %}}{%-endfor%} // end namespaces 62 | {%-endif%} 63 | 64 | boost::variant<{{optionList}}> {{className}}::Get() const 65 | { 66 | return _value; 67 | } 68 | 69 | void {{className}}::Set(const boost::variant<{{optionList}}>& variant) 70 | { 71 | _value = variant; 72 | {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%} 73 | {%if not loop.first%} 74 | else {%endif%}if (_value.which() == {{loop.index - 1}}) 75 | { 76 | boost::get<{{ObjectType(optionName, s)}}>(_value).SetHandle(_handle); 77 | } 78 | {%-endfor %} 79 | } 80 | 81 | {%-if commonType in ['boolean', 'integer', 'number', string] %} 82 | {{className}} {{className}}::FromString(const std::string& str) 83 | { 84 | {{exception}}Collection exceptionCollection; 85 | {%for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%} 86 | try 87 | { 88 | auto obj = {{ObjectType(optionName, s)}}::FromString(str); 89 | return {{className}}(obj); 90 | } 91 | catch (const std::exception& e) 92 | { 93 | // {{optionName}} wasnt able to deserialize{%if not loop.last%} 94 | // but there are other option to try{%endif%} 95 | exceptionCollection.AddException(e); 96 | } 97 | {%endfor %} 98 | 99 | // Didnt deserialize 100 | throw {{exception}}(exceptionCollection.what()); 101 | } 102 | {%-endif%} 103 | 104 | {{className}} {{className}}::FromJson(const rapidjson::Value& json) 105 | { 106 | {{exception}}Collection exceptionCollection; 107 | {%for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%} 108 | try 109 | { 110 | auto obj = {{ObjectType(optionName, s)}}::FromJson(json); 111 | return {{className}}(obj); 112 | } 113 | catch (const std::exception& e) 114 | { 115 | // {{optionName}} wasnt able to deserialize{%if not loop.last%} 116 | // but there are other option to try{%endif%} 117 | exceptionCollection.AddException(e); 118 | } 119 | {%endfor %} 120 | 121 | // Didnt deserialize 122 | throw {{exception}}(exceptionCollection.what()); 123 | } 124 | 125 | void {{className}}::ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const 126 | { 127 | {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%} 128 | {%if not loop.first%} 129 | else {%endif%}if (_value.which() == {{loop.index - 1}}) 130 | { 131 | boost::get<{{ObjectType(optionName, s)}}>(_value).ToJson(value, allocator); 132 | } 133 | {%-endfor %} 134 | } 135 | 136 | void {{className}}::SetHandle(const std::string& handle) 137 | { 138 | _handle = handle; 139 | 140 | {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%} 141 | {%if not loop.first%} 142 | else {%endif%}if (_value.which() == {{loop.index - 1}}) 143 | { 144 | boost::get<{{ObjectType(optionName, s)}}>(_value).SetHandle(handle); 145 | } 146 | {%-endfor %} 147 | } 148 | 149 | std::string {{className}}::GetHandle() const 150 | { 151 | return _handle; 152 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/oneof.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader-%} 2 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 3 | {%macro NestedObjectName(name) -%} 4 | {{-name | UpperCamelCase-}} 5 | {%-endmacro%} 6 | {%-macro ObjectType(propName, propSchema) -%} 7 | {%-if '$ref' in propSchema -%} 8 | {{-loader.Reference(resolver, propSchema['$ref'])-}} 9 | {%-else-%} 10 | {{-NestedObjectName(propName)-}} 11 | {%-endif-%} 12 | {%-endmacro%} 13 | {%-set optionList -%} 14 | {%-for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%} 15 | {{ObjectType(optionName, s)}}{%if not loop.last%}, {%endif-%} 16 | {%-endfor -%} 17 | {%-endset%} 18 | /*! {{Name}} is a wrapper around one of {{schema.oneOf|length}} different types. 19 | {%-if schema.description %} 20 | * 21 | {{schema.description | indent(1) }} 22 | * 23 | {%-endif%} 24 | */ 25 | class {{Name}} 26 | { 27 | public: 28 | {%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%} 29 | {{loader.Class('hpp', resolver, [Name], optionName, s) | indent(4) }} 30 | 31 | {%endfor%} 32 | 33 | {%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%} 34 | /*! Constructor that initializes the {{Name}} object with a {{ObjectType(optionName, s)}} instance.] 35 | */ 36 | {{Name}}(const {{ObjectType(optionName, s)}}& obj); 37 | {%endfor%} 38 | virtual ~{{Name}}() = default; 39 | 40 | {%-set commonType = schema.GetCommonType(resolver)%} 41 | {%-if commonType in ['boolean', 'integer', 'number', string] %} 42 | /*! Stream operator. 43 | */ 44 | friend std::ostream& operator<<(std::ostream& os, const {{Name}}& inst); 45 | {%-endif%} 46 | 47 | /*! Produce a hash value of the object. 48 | * \param inst A {{Name}} instance from which to produce a hash. 49 | * \return a hash number using a boost hashing algorithm. 50 | */ 51 | friend std::size_t hash_value(const {{Name}}& inst); 52 | 53 | /*! Gets a variant object containing whatever instance of whatever object has been set. 54 | */ 55 | boost::variant<{{optionList}}> Get() const; 56 | 57 | {#{%-for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%} 58 | /*! Asserts that the type provided is a {{ObjectType(optionName, s)}}. 59 | * Asserts that when `object.Get().which() == {{loop.index - 1}}` the expression `boost::get<{{ObjectType(optionName, s)}}>(object.Get())` is valid. 60 | * This is static_assertion, so it will break at compile time, which is desireable. 61 | * \code 62 | boost::variant<{{optionList}}> v = instance.Get(); 63 | if (v.which() == {{loop.index - 1}}) 64 | { 65 | {{Name}}::StaticAssertWhich{{loop.index - 1}}Type<{{ObjectType(optionName, s)}}>(); 66 | {{ObjectType(optionName, s)}} opt = boost::get<{{ObjectType(optionName, s)}}>(v); 67 | // do other stuff with opt 68 | } 69 | * \endcode 70 | */ 71 | template 72 | static inline void StaticAssertWhich{{loop.index - 1}}Type() 73 | { 74 | static_assert(std::is_same::value, "Type {{loop.index - 1}} is not {{ObjectType(optionName, s)}}"); 75 | } 76 | {%-endfor -%}#} 77 | 78 | /*! Sets the variant object. 79 | * \fn void Set(const boost::variant<{{optionList}}>& variant) 80 | * \param variant An instance of one of these types must be passed: {{optionList}} 81 | */ 82 | void Set(const boost::variant<{{optionList}}>& variant); 83 | 84 | {%-if commonType in ['boolean', 'integer', 'number', string] %} 85 | /*! \fn {{Name}} FromString(const std::string& str) 86 | * \brief Performs a "lexical cast" on the string to create a new {{Name}} object 87 | * \param const std::string& str stringified representation of a {{schema.GetCommonType(resolver)}} 88 | * \throw {{exception}} If the string cannot be cast or does not JSON-schema validate 89 | * \return {{Name}} 90 | */ 91 | static {{Name}} FromString(const std::string& str); 92 | {%-endif%} 93 | 94 | /*! \fn {{Name}} FromJson(const rapidjson::Value& json) 95 | * \brief Deserializes a JSON value into a new instance of {{Name}}. 96 | * \param json is the RapidJSON value. 97 | * \throw {{exception}} If the JSON didn't validate according to the schema of one of the component objects. 98 | * \returns {{Name}} 99 | */ 100 | static {{Name}} FromJson(const rapidjson::Value& json); 101 | 102 | /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) 103 | * \brief Sets 'value' to whatever variant object is set 104 | * \param value is the RapidJSON value which will be modified to contain the serialization 105 | * \param allocator is the top-level RapidJSON document allocator which may be used for allocations 106 | */ 107 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 108 | 109 | /*! Sets a string handle associated with this {{Name}} instance. 110 | * This gets called by a parent object after creating an instance that is used for an object's property. 111 | * \param handle is the string name. 112 | */ 113 | void SetHandle(const std::string& handle); 114 | 115 | /*! Gets the string handle associated with this {{Name}} instance. 116 | * This is often the property name used in a JSON-object parent. 117 | * It may be empty. 118 | * \returns the handle string 119 | */ 120 | std::string GetHandle() const; 121 | private: 122 | boost::variant<{{optionList}}> _value; 123 | std::string _handle; 124 | }; 125 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/propname.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro ConstPropertyName(propname)-%} 2 | PROPERTY_NAME_{{propname|CONST_CASE}} 3 | {%-endmacro-%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/ref.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader-%} 2 | {%-set refName = loader.Reference(resolver, schema['$ref'])-%} 3 | /*! {{Name}} is an alias of {{refName}}. 4 | */ 5 | using {{Name}} = {{refName}}; 6 | 7 | {{''}} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/source.cpp.jinja2: -------------------------------------------------------------------------------- 1 | // This file is automatically generated using a JSON-Schema definition. 2 | // DO NOT MODIFY this file, as changes will be overwritten on the next code 3 | // generation. 4 | {{''}} 5 | {%-for dep in deps %} 6 | #include {{dep}} 7 | {%-endfor%} 8 | 9 | {%-set originalNamespace=ns%} 10 | {%for using in resolver.cpp_get_usings() %} 11 | using namespace {{using | join("::") }}; 12 | {%-endfor%} 13 | 14 | {%-from 'loader.jinja2' import Class with context%} 15 | {{-Class('cpp', resolver, ns, Name, schema)}} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/string.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 2 | {%-set std = resolver.cpp_resolve_namespace(['std']) %} 3 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 4 | {%-if schema.const is not defined%} 5 | {{className}}::{{Name}}(const {{std}}string& value) 6 | { 7 | Set(value); 8 | } 9 | 10 | {{className}}::{{Name}}(const char* value) 11 | { 12 | Set(value); 13 | } 14 | {%endif%} 15 | {%-if schema.default is defined or schema.const is defined%} 16 | {{className}}::{{Name}}() 17 | { 18 | Set({%if schema.const is defined%}CONST_VALUE{%else%}DEFAULT_VALUE{%endif%}); 19 | } 20 | {%elif schema.format is defined and schema.format == 'uuid'%} 21 | {{className}}::{{Name}}() 22 | { 23 | SetUuid(); 24 | } 25 | {%-endif%} 26 | {{className}}::operator {{std}}string() const 27 | { 28 | return Get(); 29 | } 30 | 31 | {{className}}& {{className}}::operator=(const {{std}}string& value) 32 | { 33 | Set(value); 34 | return *this; 35 | } 36 | 37 | {{className}}& {{className}}::operator=(const char* value) 38 | { 39 | Set(value); 40 | return *this; 41 | } 42 | {%for origNs in originalNamespace %} 43 | namespace {{origNs}} { 44 | {%-endfor%} 45 | {{std}}ostream& operator<<({{std}}ostream& os, const {{className}}& str) 46 | { 47 | os << str._value; 48 | return os; 49 | } 50 | 51 | bool operator< (const {{className}}& left, const {{className}}& right) 52 | { 53 | return left._value < right._value; 54 | } 55 | 56 | {{std}}size_t hash_value(const {{className}}& str) 57 | { 58 | return boost::hash_value(str._value); 59 | } 60 | {%for origNs in originalNamespace %}}{%-endfor%} // end namespaces 61 | 62 | {{className}}& {{className}}::Set(const {{std}}string& value) 63 | { 64 | Validate(value); 65 | _value = value; 66 | return *this; 67 | } 68 | 69 | {{className}}& {{className}}::Set(const char* value) 70 | { 71 | Validate(value); 72 | _value = value; 73 | return *this; 74 | } 75 | 76 | {%-if schema.format is defined and schema.format == 'uuid'%} 77 | {{className}}& {{className}}::SetUuid() 78 | { 79 | boost::uuids::random_generator generator; 80 | boost::uuids::uuid uuid = generator(); 81 | Set(boost::uuids::to_string(uuid)); 82 | return *this; 83 | } 84 | {%-endif-%}{# #} 85 | 86 | {{std}}string {{className}}::Get() const 87 | { 88 | return _value; 89 | } 90 | 91 | void {{className}}::Validate(const {{std}}string& testValue) 92 | { 93 | {%-if schema.maxLength is defined %} 94 | if (testValue.size() > {{className}}::MAX_LENGTH) 95 | { 96 | throw {{exception}}("The string is longer than {{className}}::MAX_LENGTH={{schema.maxLength}}"); 97 | } 98 | {%-endif%} 99 | {%-if schema.minLength is defined %} 100 | if (testValue.size() < {{className}}::MIN_LENGTH) 101 | { 102 | throw {{exception}}("The string is shorter than {{className}}::MIN_LENGTH={{schema.minLength}}"); 103 | } 104 | {%-endif%} 105 | {%-if schema.pattern is defined %} 106 | {{std}}regex regexPattern("{{schema.pattern}}"); 107 | {{std}}smatch regexMatch; 108 | {{std}}regex_match(testValue, regexMatch, regexPattern); 109 | if (regexMatch.empty()) throw {{exception}}("The string value did not match the required regular expression pattern '{{schema.pattern}}'"); 110 | {%-endif%} 111 | {%-if schema.const is defined %} 112 | if (testValue != CONST_VALUE) 113 | { 114 | throw {{exception}}("The value is not '{{schema.const}}'"); 115 | } 116 | {%-endif%} 117 | } 118 | 119 | {{className}} {{className}}::FromJson(const {{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& json) 120 | { 121 | if (!(json.IsString())) 122 | { 123 | throw {{exception}}("JSON wasn't a string"); 124 | } 125 | {%if schema.const is defined%} 126 | Validate(json.GetString()); 127 | return {{className}}(); 128 | {%-else%} 129 | return {{className}}(json.GetString()); 130 | {%-endif%} 131 | } 132 | 133 | {{className}} {{className}}::FromString(const {{std}}string& str) 134 | { 135 | {%if schema.const is defined-%} 136 | Validate(str); 137 | return {{className}}(); 138 | {%-else-%} 139 | return {{className}}(str); 140 | {%-endif%} 141 | } 142 | 143 | void {{className}}::ToJson({{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 144 | { 145 | value.SetString(_value.c_str(), _value.size(), allocator); 146 | } 147 | 148 | void {{className}}::SetHandle(const std::string& handle) 149 | { 150 | _handle = handle; 151 | } 152 | 153 | std::string {{className}}::GetHandle() const 154 | { 155 | return _handle; 156 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/string.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset-%} 2 | /*! {{Name}} is a wrapper around a {{schema.type}}. 3 | {%-if schema.description %} 4 | * {{schema.description}} 5 | {%-endif%} 6 | */ 7 | class {{Name}} 8 | { 9 | public: 10 | {%-if schema.maxLength is defined %} 11 | /*! The maximum length of the contained string. 12 | * Trying to set a string longer than this length will throw. 13 | * It is useful to use this value in a static_assert statement 14 | * to ensure it is consistant with external requirements. 15 | */ 16 | static constexpr int MAX_LENGTH = {{schema.maxLength}}; 17 | {%endif%} 18 | {%-if schema.minLength is defined %} 19 | /*! The minimum length of the contained string. 20 | * Trying to set a string shorter than this length will throw. 21 | * It is useful to use this value in a static_assert statement 22 | * to ensure it is consistant with external requirements. 23 | */ 24 | static constexpr int MIN_LENGTH = {{schema.minLength}}; 25 | {%endif%} 26 | {%-if schema.default is defined or schema.const is defined%} 27 | /*! The default value of the string. 28 | * If the string has not been set to a value, "{{schema.default or schema.const}}" would be returned for value getting methods. 29 | */ 30 | static constexpr auto DEFAULT_VALUE = "{{schema.default or schema.const}}"; 31 | 32 | {%-endif%} 33 | {%-if schema.const is defined%} 34 | /*! The constant value of the string. 35 | * The value is not allowed to be anything other than "{{schema.const}}". 36 | */ 37 | static constexpr auto CONST_VALUE = "{{schema.const}}"; 38 | {%-endif%} 39 | {%-if schema.const is not defined%} 40 | 41 | /*! Constructor. 42 | * \param value initial value of {{Name}} instance. 43 | */ 44 | {{Name}}(const std::string& value); 45 | 46 | /*! Constructor. 47 | * \param value initial value of {{Name}} instance. 48 | */ 49 | {{Name}}(const char* value); 50 | {%-endif%} 51 | {%-if schema.default is defined or schema.const is defined%} 52 | 53 | /*! Constructor that sets the objects value to "{{schema.const or schema.default}}". 54 | */ 55 | {{Name}}(); 56 | {%-elif schema.format is defined and schema.format == 'uuid'%} 57 | 58 | /*! Constructor that sets the objects value to a new unique UUID. 59 | */ 60 | {{Name}}(); 61 | {%-endif-%}{# #} 62 | 63 | /*! Default destructor. 64 | */ 65 | virtual ~{{Name}}() = default; 66 | 67 | /*! Operator to cast a {{Name}} instance to a string. 68 | */ 69 | operator std::string() const; 70 | /*! Provide the string value of the {{Name}} instance to a stream. 71 | * \param os the stream object 72 | * \param str an instance of {{Name}} 73 | * \return the same ostream input object 74 | */ 75 | friend std::ostream& operator<<(std::ostream& os, const {{Name}}& str); 76 | 77 | /*! The less than comparison operator. 78 | * \param left A {{Name}} instance on the left size of the less than expression. 79 | * \param right A {{Name}} instance on the right side of the less than expression. 80 | * \return true if the less than expression is true. 81 | */ 82 | friend bool operator< (const {{Name}}& left, const {{Name}}& right); 83 | 84 | /*! Produce a hash value of the string. 85 | * \param str A {{Name}} instance from which to produce a hash. 86 | * \return a hash number using a boost hashing algorithm. 87 | */ 88 | friend std::size_t hash_value(const {{Name}}& str); 89 | 90 | /*! Get the contained string. 91 | * \return The contained string. 92 | */ 93 | std::string Get() const; 94 | 95 | /*! Validate that the provided string meets schema constraints. 96 | * This is used internally anytime a value is passed into the class, including from the constructor. 97 | * \param testValue string for evaluation 98 | * \throw {{exception}} If the string doesn't meet requirements 99 | */ 100 | static void Validate(const std::string& testValue); 101 | 102 | /*! Create a new {{Name}} object from a JSON structure. 103 | * \throw {{exception}} If the string didn't meet the schema's constraints. 104 | * \return new {{Name}} object containing values from the JSON. 105 | * \param json JSON structure to use for creating new {{Name}} object. 106 | */ 107 | static {{Name}} FromJson(const rapidjson::Value& json); 108 | 109 | /*! Create a new {{Name}} object from the string. 110 | * The string must meet the schema's constraints. 111 | * \return new {{Name}} object containing the string. 112 | * \param str the string that will be contained in the new {{Name}} object. 113 | * \throw {{exception}} If the JSON value isn't a JSON string. 114 | * \throw {{exception}} If the string didn't meet the schema's constraints. 115 | */ 116 | static {{Name}} FromString(const std::string& str); 117 | 118 | /*! Serialize the {{Name}} instance into a JSON object. 119 | * \param value is modified to be a string type containing the instance's string value. 120 | * \param allocator is used for rapidjson allocation. 121 | */ 122 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 123 | 124 | /*! Sets a string handle associated with this {{Name}} instance. 125 | * This gets called by a parent object after creating an instance that is used for an object's property. 126 | * \param handle is the string name. 127 | */ 128 | void SetHandle(const std::string& handle); 129 | 130 | /*! Gets the string handle associated with this {{Name}} instance. 131 | * This is often the property name used in a JSON-object parent. 132 | * It may be empty. 133 | * \returns the handle string 134 | */ 135 | std::string GetHandle() const; 136 | 137 | {%if schema.IsReadOnly() or schema.const is defined %} 138 | private: 139 | {%-endif%} 140 | /*! Assignment operator to set the value of the {{Name}} instance. 141 | * \param value the new string value. 142 | * \throw {{exception}} if the passed string value doesn't meet schema validation. 143 | * \return reference to the {{Name}} instance 144 | */ 145 | {{Name}}& operator=(const std::string& value); 146 | 147 | /*! Assignment operator to set the value of the {{Name}} instance. 148 | * \param value the new string value. 149 | * \throw {{exception}} if the passed string value doesn't meet schema validation. 150 | * \return reference to the {{Name}} instance 151 | */ 152 | {{Name}}& operator=(const char* value); 153 | 154 | /*! Set the value of the string. 155 | * \param value the new string value. 156 | * \throw {{exception}} If the passed string value doesn't meet schema validation. 157 | */ 158 | {{Name}}& Set(const std::string& value); 159 | 160 | /*! Set the value of the string. 161 | * \param value the new string value. 162 | * \throw {{exception}} If the passed string value doesn't meet schema validation. 163 | */ 164 | {{Name}}& Set(const char* value); 165 | 166 | {%-if schema.format is defined and schema.format == 'uuid'%} 167 | /*! Set the encapsulated value of the {{Name}} object to a new UUID. 168 | */ 169 | {{Name}}& SetUuid(); 170 | {%-endif%} 171 | 172 | private: 173 | std::string _value; 174 | std::string _handle; 175 | }; -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/string_enum.cpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set className = resolver.cpp_resolve_namespace(ns)+Name%} 2 | {%-set std = resolver.cpp_resolve_namespace(['std']) %} 3 | {%-set enumType = className+"::Value"%} 4 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 5 | {{className}}::{{Name}}({{enumType}} value) 6 | { 7 | Set(value); 8 | } 9 | 10 | {%-if schema.default is defined %} 11 | {%set emptyConstructor = true %} 12 | {{className}}::{{Name}}() 13 | { 14 | Set(DEFAULT_VALUE); 15 | } 16 | {%-endif%} 17 | 18 | {{className}}::operator {{enumType}}() const 19 | { 20 | return Get(); 21 | } 22 | 23 | {{className}}& {{className}}::operator=({{enumType}} value) 24 | { 25 | Set(value); 26 | return *this; 27 | } 28 | {%for origNs in originalNamespace %} 29 | namespace {{origNs}} { 30 | {%-endfor%} 31 | {{std}}ostream& operator<<({{std}}ostream& os, const {{className}}& v) 32 | { 33 | os << v.EnumToString(v._value); 34 | return os; 35 | } 36 | 37 | bool operator< (const {{className}}& left, const {{className}}& right) 38 | { 39 | return left._value < right._value; 40 | } 41 | 42 | {{std}}size_t hash_value(const {{className}}& e) 43 | { 44 | return boost::hash_value(e.ToString()); 45 | } 46 | {%for origNs in originalNamespace %}}{%-endfor%} // end namespaces 47 | 48 | void {{className}}::Set({{enumType}} value) 49 | { 50 | _value = value; 51 | } 52 | 53 | {{enumType}} {{className}}::Get() const 54 | { 55 | return _value; 56 | } 57 | 58 | {{std}}string {{className}}::EnumToString({{enumType}} value) 59 | { 60 | switch (value) 61 | { 62 | {%-for enum in schema.enum%} 63 | case {{enumType}}::{{enum | enumify}}: 64 | return "{{enum}}"; 65 | {%-endfor%} 66 | } 67 | // By not including a 'defaut' case in the switch statement, we should ensure that 68 | // all values of the enum are handled. However, some compilers like to see a return 69 | // statement when no cases are matched. While this never happens, throwing here 70 | // avoids a compiler warning. 71 | throw std::out_of_range("No valid string for invalid enum value"); 72 | } 73 | 74 | {{enumType}} {{className}}::StringToEnum(const std::string& input) 75 | { 76 | {%-for enum in schema.enum %} 77 | {%if not loop.first%}else {%endif%}if (input == "{{enum}}") 78 | { 79 | return {{enumType}}::{{enum | enumify}}; 80 | } 81 | {%-endfor%} 82 | else 83 | { 84 | throw std::out_of_range("Could not find enum value for string"); 85 | } 86 | } 87 | 88 | {{className}} {{className}}::FromJson(const {{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& json) 89 | { 90 | if (!(json.IsString())) 91 | { 92 | throw {{exception}}("JSON wasn't a string"); 93 | } 94 | 95 | {{std}}string testValue = json.GetString(); 96 | try 97 | { 98 | return {{className}}(StringToEnum(testValue)); 99 | } 100 | catch (const std::exception& e) 101 | { 102 | throw {{exception}}(e); 103 | } 104 | } 105 | 106 | {{className}} {{className}}::FromString(const {{std}}string& str) 107 | { 108 | return {{className}}(StringToEnum(str)); 109 | } 110 | 111 | void {{className}}::ToJson({{resolver.cpp_resolve_namespace(['rapidjson'])}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const 112 | { 113 | {{std}}string strValue = EnumToString(_value); 114 | value.SetString(strValue.c_str(), strValue.size(), allocator); 115 | } 116 | 117 | {{std}}string {{className}}::ToString() const 118 | { 119 | return EnumToString(_value); 120 | } 121 | 122 | void {{className}}::SetHandle(const std::string& handle) 123 | { 124 | _handle = handle; 125 | } 126 | 127 | std::string {{className}}::GetHandle() const 128 | { 129 | return _handle; 130 | } -------------------------------------------------------------------------------- /jsonschemacodegen/templates/cpp/string_enum.hpp.jinja2: -------------------------------------------------------------------------------- 1 | {%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%} 2 | /*! {{Name}} is a wrapper around a {{schema.type}}. 3 | * The value is limited to a set of enum values. 4 | {%-if schema.description %} 5 | * {{schema.description | doxygenify}} 6 | {%-endif%} 7 | */ 8 | class {{Name}} 9 | { 10 | public: 11 | /*! Enumeration of possible values 12 | */ 13 | enum class Value 14 | { 15 | {%-for opt in schema.enum %} 16 | {{opt |enumify}}{%if not loop.last%},{%endif%} 17 | {%-endfor%} 18 | }; 19 | 20 | /*! The number of different values. 21 | * If this list corresponds to a list of values, it may be good to static_assert on the length of both lists 22 | */ 23 | static constexpr int VALUE_ENUM_OPTION_COUNT = {{schema.enum | length}}; 24 | {%-if schema.default is defined %} 25 | /*! This string represents the default JSON value. 26 | */ 27 | static constexpr char DEFAULT_STRING[] = "{{schema.default}}"; 28 | 29 | /*! This is the default enum value. 30 | * When the class is called with an empty constructor, this is the value that is used. 31 | */ 32 | static constexpr Value DEFAULT_VALUE = Value::{{schema.default |enumify}}; 33 | {%-endif%} 34 | 35 | /*! Constructor for {{Name}}. 36 | * \param value initial value 37 | */ 38 | {{Name}}(Value value); 39 | {# #} 40 | {%-if schema.default is defined %} 41 | /*! Constructor that initializes the object with the {{schema.default |enumify}} value. 42 | */ 43 | {{Name}}(); 44 | {%-endif%} 45 | 46 | /*! Default destructor. 47 | */ 48 | virtual ~{{Name}}() = default; 49 | 50 | /*! Getter operator. 51 | */ 52 | operator Value() const; 53 | 54 | /*! Assignment operator. 55 | * \param value new value 56 | */ 57 | {{Name}}& operator=(Value value); 58 | 59 | /*! Stream operator 60 | * \param os the output stream 61 | * \param v instance of {{Name}} 62 | */ 63 | friend std::ostream& operator<<(std::ostream& os, const {{Name}}& v); 64 | 65 | /*! The less than comparison operator. 66 | * \param left A {{Name}} instance on the left size of the less than expression. 67 | * \param right A {{Name}} instance on the right side of the less than expression. 68 | * \return true if the less than expression is true. 69 | */ 70 | friend bool operator< (const {{Name}}& left, const {{Name}}& right); 71 | 72 | /*! Produce a hash value of the object. 73 | * \param e A {{Name}} instance from which to produce a hash. 74 | * \return a hash number using a boost hashing algorithm. 75 | */ 76 | friend std::size_t hash_value(const {{Name}}& e); 77 | 78 | /*! Sets the value to the provided enumerated Value. 79 | * \param value new value. 80 | */ 81 | void Set(Value value); 82 | 83 | /*! Get the enumerated Value. 84 | */ 85 | Value Get() const; 86 | 87 | /*! Returns the json string value for the enumerated value. 88 | * \param value is the value for which a string should be returned. 89 | */ 90 | static std::string EnumToString(Value value); 91 | 92 | /*! Returns an enumerated value matching the provided string. 93 | * \param str is the string to match. 94 | * \throws std::out_of_range If the provided string does not match an enumerated value. 95 | */ 96 | static Value StringToEnum(const std::string& str); 97 | 98 | /*! Initializes a {{Name}} object from JSON. 99 | * \param json JSON string value that maps to an enumerated value. 100 | * \throws {{exception}} If the JSON value isn't one of the supported string values. 101 | */ 102 | static {{Name}} FromJson(const rapidjson::Value& json); 103 | 104 | /*! Initializes a {{Name}} object from a string value. 105 | * \param str must match one of the supported string values. 106 | * \throws {{exception}} if the provided value isn't a supported value. 107 | */ 108 | static {{Name}} FromString(const std::string& str); 109 | 110 | /*! Returns a JSON string representation of this object. 111 | * \param value is modified with the JSON string representation. 112 | * \param allocator is the rapidjson allocator object from the parent document. 113 | */ 114 | void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const; 115 | 116 | /*! Returns a string representation of this object. 117 | * \return string representation without quotes. 118 | */ 119 | std::string ToString() const; 120 | 121 | /*! Sets a string handle associated with this {{Name}} instance. 122 | * This gets called by a parent object after creating an instance that is used for an object's property. 123 | * \param handle is the string name. 124 | */ 125 | void SetHandle(const std::string& handle); 126 | 127 | /*! Gets the string handle associated with this {{Name}} instance. 128 | * This is often the property name used in a JSON-object parent. 129 | * It may be empty. 130 | * \returns the handle string 131 | */ 132 | std::string GetHandle() const; 133 | private: 134 | Value _value; 135 | std::string _handle; 136 | }; -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pearmaster/json-schema-codegen/732829c2a4cce5e2189d75dc05fdd846dbdf3c66/jsonschemacodegen/templates/markdown/__init__.py -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/allOf.md.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'schema.jinja2' as loader-%} 2 | **Object**{%if 'title' in schema%} - {{schema.title |italics}}. Has{%else%} with{%endif%} these properties: 3 | 4 | 5 | {%-for s in schema.GetComponents() %} 6 | {%-set subSchema = s.Resolve(resolver).Resolve(resolver)%} 7 | {%-if 'properties' in subSchema%} 8 | {%-for propName, prop in subSchema.properties.items() %} 9 | {{loader.Property(propName, prop.Resolve(resolver), propName in subSchema.required or propName in subSchema.requiredProperties, resolver)}} 10 | {%-endfor%} 11 | {%-else-%} 12 | 19 | {%-endif-%} 20 | {%endfor%} 21 |
Property NameType
13 | 14 | 15 | {{loader.Display(subSchema, resolver)}} 16 | 17 | 18 |
22 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/anyOf.md.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'schema.jinja2' as loader-%} 2 | An **object**{%if 'title' in schema%} - {{schema.title|italics}}. It is {%endif%} made up of properties from one or more of these groups: 3 | {%for s in schema.GetComponents() %} 4 | {{loader.Display(s.Resolve(resolver), resolver)}} 5 | {%endfor%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/array.md.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro numItems(schema)-%} 2 | {%-if schema.minItems is defined-%} 3 | {%-if schema.maxItems is defined-%} 4 | {%-if schema.minItems == schema.maxItems -%} 5 | There must be exactly {{schema.minItems}} elements. 6 | {%-else-%} 7 | There must be {{schema.minItems}} to {{schema.maxItems}} elements. 8 | {%-endif-%} 9 | {%-else-%}{#no maxitems#} 10 | There must be at least {{schema.minItems}} elements. 11 | {%-endif-%} 12 | {%-elif schema.maxItems is defined-%} 13 | There must be at most {{schema.maxItems}} elements. 14 | {%-endif-%} 15 | {%-endmacro-%} 16 | {%-import 'schema.jinja2' as loader-%} 17 | **Array**{%if 'title' in schema%} - {{schema.title|italics}}.{%endif%} {%if not schema.IsTupleSchema() %}{{numItems(schema)}} Each element is: 18 | 19 | {{loader.Display(schema.GetItemSchema().Resolve(resolver), resolver) | replace("\n", "\n> ")}} 20 | {%-else%} The array must have {{schema.NumberTupleItems()}} elements: {%for item in schema.GetTupleSchema()%} 21 | Element {{loop.index}}: 22 | {{loader.Display(item.Resolve(resolver), resolver) | replace("\n", "\n> ")}} 23 | {%endfor%}{%endif%} 24 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/boolean.md.jinja2: -------------------------------------------------------------------------------- 1 | **boolean**{%if 'title' in schema%} - *{{schema.title}}*{%endif%} 2 | {%-if 'default' in schema%} 3 | * Default value: `{{schema.default | lower }}` 4 | {%-endif-%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/description.md.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'schema.jinja2' as loader-%} 2 | {%-set resolvedSchema = schema.Resolve(resolver)-%} 3 | {{loader.Display(resolvedSchema, resolver)}} 4 | {%if 'description' in resolvedSchema%}{{resolvedSchema.description}}{%endif%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/integer.md.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro IntFormat(schema)-%} 2 | {%-if 'format' in schema-%} 3 | {%-if schema['format'].startswith('u')%}Unsigned {%endif-%} 4 | {%-if schema['format'].endswith('64')%}64-bit {%endif-%} 5 | {%-if schema['format'].endswith('32')%}32-bit {%endif-%} 6 | {%-if schema['format'].endswith('16')%}16-bit {%endif-%} 7 | {%-if schema['format'].endswith('8')%}8-bit {%endif-%} 8 | {%-endif-%} 9 | {%-endmacro-%} 10 | **{{IntFormat(schema)}}integer**{%if 'title' in schema%} - *{{schema.title}}*{%endif%} 11 | {%-if 'default' in schema%} 12 | * Default value: `{{schema.default}}` 13 | {%-endif-%} 14 | {%-if 'minimum' in schema%} 15 | * Greater than or equal to {{schema.minimum}} 16 | {%-endif-%} 17 | {%if 'maximum' in schema%} 18 | * Less than or equal to {{schema.maximum}} 19 | {%-endif-%} 20 | {%if 'exclusiveMinimum' in schema%} 21 | * Greater than {{schema.exclusiveMinimum}} 22 | {%-endif-%} 23 | {%if 'exclusiveMaximum' in schema%} 24 | * Less than {{schema.maximum}} 25 | {%-endif-%} 26 | {%if 'multipleOf' in schema%} 27 | * Multiple of {{schema.multipleOf}} 28 | {%-endif-%} 29 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/null.md.jinja2: -------------------------------------------------------------------------------- 1 | **null**{%if 'title' in schema%} - {{schema.title|italics}}{%endif%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/number.md.jinja2: -------------------------------------------------------------------------------- 1 | **number**{%if 'title' in schema%} - {{schema.title|italics}}{%endif%} 2 | {%-if 'default' in schema%} 3 | * Default value: `{{schema.default}}` 4 | {%-endif-%} 5 | {%-if 'minimum' in schema%} 6 | * Greater than or equal to {{schema.minimum}} 7 | {%-endif-%} 8 | {%if 'maximum' in schema%} 9 | * Less than or equal to {{schema.maximum}} 10 | {%-endif-%} 11 | {%if 'exclusiveMinimum' in schema%} 12 | * Greater than {{schema.exclusiveMinimum}} 13 | {%-endif-%} 14 | {%if 'exclusiveMaximum' in schema%} 15 | * Less than {{schema.maximum}} 16 | {%-endif-%} 17 | {%if 'multipleOf' in schema%} 18 | * Multiple of {{schema.multipleOf}} 19 | {%-endif-%} 20 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/object.md.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'schema.jinja2' as loader-%} 2 | **Object**{%if 'title' in schema%} - {{schema.title | italics}}. Has{%else%} with{%endif%} these properties: 3 | 4 | 5 | {%-for propName, prop in schema.properties.items() %} 6 | {{loader.Property(propName, prop.Resolve(resolver), propName in schema.required or propName in schema.requiredProperties, resolver)}} 7 | {%-endfor%} 8 |
Property NameTypeDescription
9 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/oneOf.md.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'schema.jinja2' as loader-%} 2 | One of the following types: 3 | {%- for s in schema.GetComponents() %} 4 | * OR {{loader.Display(s.Resolve(resolver), resolver) | mdindent(2)}} 5 | {# #} 6 | {%-endfor%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/schema.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro Display(schema, resolver=None) %} 2 | {%-if schema.always_valid is defined -%} 3 | {%if schema.always_valid %}Can be anything{%else%}🚫 Must not be present{%endif%}. 4 | {%-elif schema.oneOf is defined -%} 5 | {%-include 'oneOf.md.jinja2'-%} 6 | {%-elif schema.allOf is defined -%} 7 | {%-include 'allOf.md.jinja2'-%} 8 | {%-elif schema.anyOf is defined -%} 9 | {%-include 'anyOf.md.jinja2'-%} 10 | {%-elif 'type' in schema %} 11 | {%-include schema.type+'.md.jinja2'-%} 12 | {%-else-%} 13 | {{schema}} 14 | {%-endif-%} 15 | {%-endmacro-%} 16 | {#- -#} 17 | {%-macro Property(propName, prop, required=False, resolver=None)%} 18 | 19 | 20 | 21 | **{{propName}}** {%if required %}(required){%endif%} 22 | 23 | {%if 'title' in prop%}*{{prop.title}}* 24 | {%endif%} 25 | 26 | 27 | 28 | {{Display(prop, resolver)}} 29 | 30 | 31 | 32 | {{prop.description}} 33 | 34 | 35 | 36 | {%-endmacro-%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/markdown/string.md.jinja2: -------------------------------------------------------------------------------- 1 | **string**{%if 'title' in schema%} - {{schema.title|italics}}{%endif%} 2 | {%-if 'const' in schema%} 3 | * Value must be `{{schema.const}}` 4 | {%-elif 'enum' in schema%} 5 | * Value must be one of: {%for value in schema.enum%}`{{value}}` {%endfor%} 6 | {%-endif-%} 7 | {%-if 'const' not in schema and 'default' in schema%} 8 | * Default value is `{{schema.default}}` 9 | {%-endif-%} 10 | {%-if 'minLength' in schema and 'maxLength' in schema%} 11 | * Length between {{schema.minLength}}-{{schema.maxLength}} 12 | {%-elif 'minLength' in schema%} 13 | * Length longer than {{schema.minLength}} 14 | {%-elif 'maxLength' in schema%} 15 | * Length shorter than {{schema.maxLength}} 16 | {%-endif-%} 17 | {%-if 'pattern' in schema%} 18 | * Meets this regular expression: `{{schema.pattern}}` 19 | {%-endif%} 20 | {%-if 'format' in schema-%} 21 | {%-if schema.format == 'uuid'%} 22 | * Must be a **UUID** 23 | {%-elif schema.format == 'date-time'%} 24 | * Must be a ISO 8601 Timestamp 25 | {%-else%} 26 | * Format is `{{schema.format}}` 27 | {%-endif%} 28 | {%-endif%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pearmaster/json-schema-codegen/732829c2a4cce5e2189d75dc05fdd846dbdf3c66/jsonschemacodegen/templates/python/__init__.py -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/array.py.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader-%} 2 | 3 | {%-if 'items' in schema-%} 4 | {%-if '$ref' in schema['items']-%} 5 | {%-set itemType = resolver.py_class_name(schema['items']['$ref'])-%} 6 | {%-endif-%} 7 | {%-endif-%} 8 | 9 | {%-set fullClassPath -%} 10 | {%-if parentClass is not defined or parentClass is none-%} 11 | {{Name}} 12 | {%-else-%} 13 | {{parentClass}}.{{Name}} 14 | {%-endif-%} 15 | {%-endset-%} 16 | 17 | class {{Name}} (UserList): 18 | """ This represents a JSON array. 19 | """ 20 | {%-if 'items' in schema%} 21 | {%-if '$ref' not in schema['items']%} 22 | {%-set itemType = 'self.Item'%} 23 | {{loader.Class(resolver, 'Item', schema['items'], fullClassPath) |indent(4)}} 24 | {%-endif%} 25 | {%-endif%} 26 | 27 | def __init__(self, the_list=None): 28 | """Initializer for array. 29 | """ 30 | if not hasattr(the_list, '__iter__'): 31 | raise TypeError("The provided list was not iterable") 32 | 33 | self.the_list = the_list 34 | 35 | if isinstance(the_list, type(self)): 36 | super().__init__(the_list.data) 37 | else: 38 | {%-if schema.minItems is defined%} 39 | if len(the_list) < {{schema.minItems}}: 40 | raise ValueError("Provided array is too short") 41 | {%-endif%} 42 | {%-if schema.maxItems is defined%} 43 | if len(the_list) > {{schema.maxItems}}: 44 | raise ValueError("Provided array is too long") 45 | {%-endif%} 46 | super().__init__([{{itemType}}(x) for x in the_list]) 47 | 48 | def Append(self, new_value) -> {{Name}}: 49 | {%-if schema.maxItems is defined%} 50 | if (len(self.the_list)+1) > {{schema.maxItems}}: 51 | raise ValueError("Appending would make the array too long") 52 | {%-endif%} 53 | self.data.append({{itemType}}(new_value)) 54 | return self 55 | 56 | def Serializable(self) -> list: 57 | return self.data 58 | 59 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/axxof.py.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader-%} 2 | 3 | {%-macro Combination(schema)-%} 4 | {%-if 'allOf' in schema-%} 5 | allOf 6 | {%-elif 'anyOf' in schema-%} 7 | anyOf 8 | {%-endif-%} 9 | {%-endmacro-%} 10 | 11 | {%-macro ComponentType(schema, index, fullClassPath=None)-%} 12 | {%-if '$ref' in schema-%} 13 | {{resolver.py_class_name(schema['$ref'])}} 14 | {%-else-%} 15 | self.Component{{index}} 16 | {%-endif-%} 17 | {%-endmacro-%} 18 | 19 | {%-set fullClassPath -%} 20 | {%-if parentClass is not defined or parentClass is none-%} 21 | {{Name}} 22 | {%-else-%} 23 | {{parentClass}}.{{Name}} 24 | {%-endif-%} 25 | {%-endset-%} 26 | 27 | class {{Name}}(object): 28 | """This combines {{Combination(schema)}} {{schema[Combination(schema)] |count}} components. 29 | """ 30 | 31 | {%-for comp in schema[Combination(schema)]%} 32 | {%-if '$ref' not in comp%} 33 | {%-set compClassName%}Component{{loop.index}}{%endset%} 34 | {{loader.Class(resolver, compClassName, comp, fullClassPath) |indent(4)}} 35 | {%-endif%} 36 | {%-endfor%} 37 | 38 | def __init__(self, *args): 39 | """Initialization for {{Name}} which combines {{Combination(schema)}} {{schema[Combination(schema)] |count}} components. 40 | """ 41 | {%-for compSchema in schema[Combination(schema)]%} 42 | self._component{{loop.index}} = None 43 | {%-endfor%} 44 | if len(args) == 1 and isinstance(args[0], type(self)): 45 | {%-for compSchema in schema[Combination(schema)]%} 46 | self._component{{loop.index}} = args[0]._component{{loop.index}} 47 | {%-endfor%} 48 | elif len(args) == 1: 49 | {%-for compSchema in schema[Combination(schema)]%} 50 | try: 51 | self._component{{loop.index}} = {{ComponentType(compSchema, loop.index)}}(args[0]) 52 | except: 53 | {%-if Combination(schema) == 'allOf'%} 54 | raise ValueError("Could not find {{ComponentType(compSchema, loop.index)}} in data") 55 | {%-else%} 56 | pass 57 | {%-endif%} 58 | {%-endfor%} 59 | else: 60 | {%-for compSchema in schema[Combination(schema)]%} 61 | self._component{{loop.index}} = None 62 | {%-endfor%} 63 | for arg in args: 64 | {%-for compSchema in schema[Combination(schema)]%} 65 | if self._component{{loop.index}} is None: 66 | if isinstance(arg, {{ComponentType(compSchema, loop.index)}}): 67 | self._component{{loop.index}} = arg 68 | else: 69 | try: 70 | self._component{{loop.index}} = {{ComponentType(compSchema, loop.index)}}(arg) 71 | except: 72 | pass 73 | {%-endfor%} 74 | {%-if Combination(schema) == 'allOf'%} 75 | {%-for compSchema in schema['allOf']%} 76 | if self._component{{loop.index}} is None: 77 | raise ValueError("Did not provide data for {{ComponentType(compSchema, loop.index)}}") 78 | {%-endfor-%} 79 | {%-endif%} 80 | {%-if schema.requiredProperties | length > 0 %} 81 | # Test to make sure item can still serialize with the required properties 82 | self.Serializable() 83 | {%-endif%} 84 | 85 | {%for compSchema in schema[Combination(schema)]%} 86 | def GetComponent{{loop.index}}(self): 87 | return self._component{{loop.index}} 88 | 89 | def SetComponent{{loop.index}}(self, data) -> {{fullClassPath}}: 90 | if isinstance(data, {{ComponentType(compSchema, loop.index)}}): 91 | self._component{{loop.index}} = data 92 | elif isinstance(data, dict): 93 | self._component{{loop.index}} = {{ComponentType(compSchema, loop.index)}}(data) 94 | else: 95 | raise ValueError("Didn't pass an valid representation of {{ComponentType(compSchema, loop.index)}}") 96 | return self 97 | {%endfor%} 98 | def Serializable(self) -> dict: 99 | obj = {} 100 | {%-for compSchema in schema[Combination(schema)]%} 101 | if self._component{{loop.index}} is not None: 102 | obj.update(self._component{{loop.index}}.Serializable()) 103 | {%-endfor%} 104 | {%-if schema.requiredProperties | length > 0 %} 105 | for req_prop in {{schema.requiredProperties |sort}}: 106 | if req_prop not in obj: 107 | raise KeyError("Missing required property '{}' in serialized object".format(req_prop)) 108 | {%-endif%} 109 | return obj 110 | 111 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/file.py.jinja2: -------------------------------------------------------------------------------- 1 | {%-if '$ref' in schema %} 2 | {%-include 'reference.py.jinja2'-%} 3 | {%-else%} 4 | {%-import 'loader.jinja2' as loader-%} 5 | {%-import 'import.py.jinja2' as importer-%} 6 | {%- set includes = []-%} 7 | {%- set include_set = []-%} 8 | {{importer.GetImports(resolver, schema, includes)}} 9 | {%-for include in includes-%} 10 | {%-if include not in include_set-%} 11 | {%-do include_set.append(include)-%} 12 | {{include + "\n"}} 13 | {%-endif-%} 14 | {%-endfor%} 15 | {{loader.Class(resolver, Name, schema)}} 16 | {%-endif-%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/import.py.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro Combination(schema)-%} 2 | {%-if 'allOf' in schema-%} 3 | allOf 4 | {%-elif 'anyOf' in schema-%} 5 | anyOf 6 | {%-endif-%} 7 | {%-endmacro-%} 8 | 9 | {%-macro GetImports(resolver, schema, includes)-%} 10 | {%-do includes.append('from __future__ import annotations')-%} 11 | {%-if schema.oneOf is defined-%} 12 | {%- do includes.append('from collections import UserDict')-%} 13 | {%- for comp in schema['oneOf']-%} 14 | {%- if '$ref' in comp-%} 15 | {%- do includes.append(resolver.py_include_statement(comp['$ref']))-%} 16 | {%- else-%} 17 | {{ GetImports(resolver, comp, includes)}} 18 | {%- endif-%} 19 | {%- endfor-%} 20 | {%-elif schema.allOf is defined-%} 21 | {%- for comp in schema[Combination(schema)]-%} 22 | {%- if '$ref' in comp-%} 23 | {%- do includes.append(resolver.py_include_statement(comp['$ref']))-%} 24 | {%- else-%} 25 | {{ GetImports(resolver, comp, includes)}} 26 | {%- endif-%} 27 | {%- endfor-%} 28 | {%-elif schema.anyOf is defined-%} 29 | {%- do includes.append('from collections import UserDict')-%} 30 | {%- for comp in schema[Combination(schema)]-%} 31 | {%- if '$ref' in comp-%} 32 | {%- do includes.append(resolver.py_include_statement(comp['$ref']))-%} 33 | {%- endif-%} 34 | {%- endfor-%} 35 | {%- for comp in schema[Combination(schema)]-%} 36 | {%- if '$ref' not in comp-%} 37 | {{ GetImports(resolver, comp, includes)}} 38 | {%- endif-%} 39 | {%- endfor-%} 40 | {%-elif '$ref' in schema-%} 41 | {# This object makes use of an external schema specified at {{schema['$ref']}}#} 42 | {%-elif 'type' not in schema-%} 43 | {%-elif schema.type == 'integer' 44 | or schema.type == 'number' 45 | or schema.type == 'boolean' 46 | or schema.type == 'string'-%} 47 | {%- if schema.pattern is defined-%} 48 | {%- do includes.append('import re')-%} 49 | {%- endif-%} 50 | {%-elif schema.type == 'array'-%} 51 | {%- do includes.append('from collections import UserList')-%} 52 | {%- if 'items' in schema-%} 53 | {%- if '$ref' in schema['items']-%} 54 | {%- do includes.append(resolver.py_include_statement(schema['items']['$ref']))-%} 55 | {%- endif-%} 56 | {%- endif-%} 57 | {%- if 'items' in schema-%} 58 | {%- if '$ref' not in schema['items']-%} 59 | {%- set itemType = 'self.Item'-%} 60 | {{ GetImports(resolver, schema['items'], includes)}} 61 | {%- endif-%} 62 | {%- endif-%} 63 | {%-elif schema.type == 'object'-%} 64 | {%- do includes.append('from collections import UserDict')-%} 65 | {%- for prop in schema.properties.values()-%} 66 | {%- if '$ref' in prop-%} 67 | {%- do includes.append(resolver.py_include_statement(prop['$ref']))-%} 68 | {%- endif%} 69 | {%- endfor%} 70 | {%- for propName, propSchema in schema.properties.items()-%} 71 | {%- if '$ref' not in prop-%} 72 | {{ GetImports(resolver, propSchema, includes)}} 73 | {%- endif-%} 74 | {%- endfor%} 75 | {%-elif schema.type == 'null'-%} 76 | {# Nothing special here #} 77 | {%-elif schema.type == 'string'-%} 78 | {%- if schema.pattern is defined-%} 79 | {%- do includes.append('import re')-%} 80 | {%- endif-%} 81 | {%-else-%} 82 | {%-endif-%} 83 | {%-endmacro-%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/loader.jinja2: -------------------------------------------------------------------------------- 1 | {%-macro Class(resolver, Name, schema, parentClass=None) -%} 2 | {%-if schema.oneOf is defined -%} 3 | {%-include 'oneof.py.jinja2'-%} 4 | {%-elif schema.allOf is defined -%} 5 | {%-include 'axxof.py.jinja2'-%} 6 | {%-elif schema.anyOf is defined -%} 7 | {%-include 'axxof.py.jinja2'-%} 8 | {%-elif '$ref' in schema %} 9 | # This object makes use of an external schema specified at {{schema['$ref']}} 10 | {%-elif 'type' not in schema-%} 11 | {%-elif schema.type == 'integer' 12 | or schema.type == 'number' 13 | or schema.type == 'boolean' 14 | or schema.type == 'string'-%} 15 | {%-include 'primative.py.jinja2'-%} 16 | {%-elif schema.type == 'array'-%} 17 | {%-include 'array.py.jinja2'-%} 18 | {%-elif schema.type == 'object'-%} 19 | {%-include 'object.py.jinja2'-%} 20 | {%-elif schema.type == 'null'-%} 21 | {%-include 'null.py.jinja2'-%} 22 | {%-elif schema.type == 'string'-%} 23 | {%-include 'primative.py.jinja2'-%} 24 | {%-else-%} 25 | # FIXME. Unable to generate a schema with type '{{schema.type}}' 26 | {%-endif-%} 27 | {%-endmacro-%} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/null.py.jinja2: -------------------------------------------------------------------------------- 1 | class {{Name}}(object): 2 | """ This represents a JSON NULL value. 3 | """ 4 | 5 | def __init__(self, data=None): 6 | if not isinstance(data, type(self)) and data is not None: 7 | raise ValueError("Expected None") 8 | 9 | def Get(self) -> None: 10 | return None 11 | 12 | def Serializable(self): 13 | return self.Get() 14 | {# #} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/object.py.jinja2: -------------------------------------------------------------------------------- 1 | {%-import 'loader.jinja2' as loader-%} 2 | 3 | {%-macro PropertyClassName(propName)-%} 4 | {{propName|PascalCase}}Property 5 | {%-endmacro-%} 6 | 7 | {%-macro PropertyType(propName, propSchema)-%} 8 | {%-if '$ref' in propSchema-%} 9 | {{resolver.py_class_name(propSchema['$ref'])}} 10 | {%-else-%} 11 | self.{{PropertyClassName(propName)}} 12 | {%-endif-%} 13 | {%-endmacro-%} 14 | 15 | {%-set fullClassPath -%} 16 | {%-if parentClass is not defined or parentClass is none-%} 17 | {{Name}} 18 | {%-else-%} 19 | {{parentClass}}.{{Name}} 20 | {%-endif-%} 21 | {%-endset-%} 22 | 23 | class {{Name}}(UserDict): 24 | """This represents a JSON object. 25 | """ 26 | 27 | {%-for propName, propSchema in schema.properties.items()%} 28 | {%-if '$ref' not in prop%} 29 | {{loader.Class(resolver, PropertyClassName(propName), propSchema, fullClassPath) |indent(4)}} 30 | {%-endif%} 31 | {%-endfor%} 32 | 33 | def __init__(self, data=None, **kwargs): 34 | """Initialization for the {{Name}} object. 35 | It can be initialized with an object, or by passing each 36 | object property as a keyword argument. 37 | """ 38 | new_data = {} 39 | {%-for propName, propSchema in schema.properties.items()%} 40 | try: 41 | prop = data["{{propName}}"] if ("{{propName}}" in data) else kwargs["{{propName}}"] 42 | if not isinstance(prop, {{PropertyType(propName, propSchema)}}): 43 | new_data["{{propName}}"] = {{PropertyType(propName, propSchema)}}(prop) 44 | except KeyError: 45 | {%-if propName in schema.required%} 46 | raise ValueError("Missing property '{{propName}}'") 47 | {%-else%} 48 | pass 49 | {%-endif%} 50 | {%-endfor%} 51 | super().__init__(new_data) 52 | 53 | {%-for propName, propSchema in schema.properties.items()%} 54 | 55 | def Get{{propName|PascalCase}}(self): 56 | return self.data["{{propName}}"] 57 | 58 | def Set{{propName|PascalCase}}(self, new_value) -> {{fullClassPath}}: 59 | if not isinstance(new_value, {{PropertyType(propName, propSchema)}}): 60 | self.data["{{propName}}"] = {{PropertyType(propName, propSchema)}}(new_value) 61 | else: 62 | self.data["{{propName}}"] = new_value 63 | return self 64 | {%-endfor%} 65 | 66 | def Serializable(self) -> dict: 67 | return self.data 68 | {# #} -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/oneof.py.jinja2: -------------------------------------------------------------------------------- 1 | 2 | {%-import 'loader.jinja2' as loader-%} 3 | 4 | {%-macro ComponentName(schema, index)-%} 5 | {%-if 'title' in schema-%} 6 | {{schema.title|PascalCase}}Option 7 | {%-else-%} 8 | Option{{index}} 9 | {%-endif-%} 10 | {%-endmacro-%} 11 | 12 | {%-macro ComponentType(schema, index)-%} 13 | {%-if '$ref' in schema-%} 14 | {{resolver.py_class_name(schema['$ref'])}} 15 | {%-else-%} 16 | self.{{ComponentName(schema, index)}} 17 | {%-endif-%} 18 | {%-endmacro-%} 19 | 20 | {%-set fullClassPath -%} 21 | {%-if parentClass is not defined or parentClass is none-%} 22 | {{Name}} 23 | {%-else-%} 24 | {{parentClass}}.{{Name}} 25 | {%-endif-%} 26 | {%-endset-%} 27 | 28 | class {{Name}}(object): 29 | """This represents one of {{schema['oneOf'] | length}} options" 30 | {%-for comp in schema['oneOf']%} 31 | * {{ComponentName(comp, loop.index)}} 32 | {%-endfor%} 33 | """ 34 | {%-for comp in schema['oneOf']%} 35 | {%-if '$ref' not in comp%} 36 | {{loader.Class(resolver, ComponentName(comp, loop.index), comp, fullClassPath) |indent(4)}} 37 | {%-endif%} 38 | {%-endfor%} 39 | 40 | def __init__(self, initial): 41 | """Constructor. Some initial data for one of the options must be passed. 42 | """ 43 | self.Set(initial) 44 | 45 | def Get(self): 46 | """Gets one of the supported option objects. 47 | """ 48 | return self._value 49 | 50 | def Set(self, data) -> {{fullClassPath}}: 51 | """ Must pass an object or data for one of the supported 52 | option types. 53 | """ 54 | if isinstance(data, type(self)): 55 | self._value = data._value 56 | self._type = data._type 57 | else: 58 | self._type = None 59 | self._value = None 60 | {%-for compSchema in schema['oneOf']%} 61 | if self._type is None: 62 | try: 63 | self._value = {{ComponentType(compSchema, loop.index)}}(data) 64 | except (ValueError, TypeError): 65 | pass 66 | else: 67 | self._type = {{ComponentType(compSchema, loop.index)}} 68 | {%-endfor%} 69 | if self._type is None: 70 | raise ValueError("Provided data did not match one of the required formats") 71 | return self 72 | 73 | def Serializable(self): 74 | """ Returns an object that can be JSON serialized. 75 | For example, use: `json.dumps(thisObject, default=lambda a: a.Serializable())` 76 | """ 77 | return self._value.Serializable() 78 | 79 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/primative.py.jinja2: -------------------------------------------------------------------------------- 1 | {%macro ToPythonType(schemaType)-%} 2 | {%-if schemaType == 'string'-%} 3 | str 4 | {%-elif schemaType == 'integer'-%} 5 | int 6 | {%-elif schemaType == 'number'-%} 7 | float 8 | {%-elif schemaType == 'boolean'-%} 9 | bool 10 | {%-endif-%} 11 | {%-endmacro-%} 12 | 13 | {%-macro FormatValue(schemaType, value)%} 14 | {%-if schemaType == 'string'-%}"{{value}}" 15 | {%-elif schemaType == 'boolean'-%} 16 | {%-if value or value == 'true'%}True{%endif-%} 17 | {%-if not value or value == 'false'%}False{%endif-%} 18 | {%-else-%} 19 | {{-value-}} 20 | {%-endif-%} 21 | {%-endmacro%} 22 | 23 | {%-macro ValidateValue(schema, varName)%} 24 | if not isinstance({{varName}}, {{ToPythonType(schema.type)}}): 25 | raise ValueError("Passed value '{}' was not a {{schema.type}}".format({{varName}})) 26 | {%-if schema.format is defined%} 27 | {%-if schema.format.startswith('uint')%} 28 | if {{varName}} < 0: 29 | raise ValueError("Value '{}' is signed but the format is {}".format({{varName}}, "{{schema.format}}")) 30 | {%-if schema.format.endswith('32')%} 31 | if {{varName}} > 0xFFFFFFFF: 32 | raise ValueError("Value '{}' is greater than the max for {}".format({{varName}}, "{{schema.format}}")) 33 | {%-endif%} {#format is uint32#} 34 | {%-if schema.format.endswith('16')%} 35 | if {{varName}} > 0xFFFF: 36 | raise ValueError("Value '{}' is greater than the max for {}".format({{varName}}, "{{schema.format}}")) 37 | {%-endif%} {#format is uint16#} 38 | {%-if schema.format.endswith('8')%} 39 | if {{varName}} > 0xFF: 40 | raise ValueError("Value '{}' is greater than the max for {}".format({{varName}}, "{{schema.format}}")) 41 | {%-endif%} {#format is uint8#} 42 | {%-elif schema.format.startswith('int')%} 43 | {%-if schema.format.endswith('32')%} 44 | if {{varName}} > 0x7FFFFFFF or {{varName}} < (-1*0x800000000): 45 | raise ValueError("Value '{}' is outside the range for {}".format({{varName}}, "{{schema.format}}")) 46 | {%-endif%} {#format is int32#} 47 | {%-if schema.format.endswith('16')%} 48 | if {{varName}} > 0x7FFF or {{varName}} < (-1*0x80000): 49 | raise ValueError("Value '{}' is outside the range for {}".format({{varName}}, "{{schema.format}}")) 50 | {%-endif%} {#format is int16#} 51 | {%-if schema.format.endswith('8')%} 52 | if {{varName}} > 0x7F or {{varName}} < (-1*0x800): 53 | raise ValueError("Value '{}' is outside the range for {}".format({{varName}}, "{{schema.format}}")) 54 | {%-endif%} {#format is int8#} 55 | {%-endif%} {#format is int/uint#} 56 | {%-endif%} {#format#} 57 | {%-if schema.maxLength is defined%} 58 | if len({{varName}}) > {{schema.maxLength}}: 59 | raise ValueError("Value '{}' length {} is greater than maximum of {{schema.maxLength}}".format({{varName}}, len({{varName}}))) 60 | {%-endif%} {#maxLength#} 61 | {%-if schema.minLength is defined%} 62 | if len({{varName}}) < {{schema.minLength}}: 63 | raise ValueError("Value '{}' length {} is less than minimum of {{schema.minLength}}".format({{varName}}, len({{varName}}))) 64 | {%-endif%} {#minLength#} 65 | {%-if schema.minimum is defined%} 66 | if {{varName}} < {{schema.minimum}}: 67 | raise ValueError("Value '{}' is less than the minimum of {{schema.minimum}}".format({{varName}})) 68 | {%-endif%} {#minimum#} 69 | {%-if schema.maximum is defined%} 70 | if {{varName}} > {{schema.maximum}}: 71 | raise ValueError("Value '{}' is more than the maximum of {{schema.maximum}}".format({{varName}})) 72 | {%-endif%} {#maximum#} 73 | {%-if schema.exclusiveMinimum is defined%} 74 | if {{varName}} <= {{schema.exclusiveMinimum}}: 75 | raise ValueError("Value '{}' is less than the minimum of {{schema.exclusiveMinimum}}".format({{varName}})) 76 | {%-endif%} {#minimum#} 77 | {%-if schema.exclusiveMaximum is defined%} 78 | if {{varName}} >= {{schema.exclusiveMaximum}}: 79 | raise ValueError("Value '{}' is more than the maximum of {{schema.exclusiveMaximum}}".format({{varName}})) 80 | {%-endif%} {#maximum#} 81 | {%-if schema.multipleOf is defined%} 82 | if ({{varName}} % {{schema.multipleOf}}) != 0: 83 | raise ValueError("Value '{}' is not a multiple of {{schema.multipleOf}}".format({{varName}})) 84 | {%-endif%} {#maximum#} 85 | {%-if schema.enum is defined%} 86 | acceptable_values = [{%for v in schema.enum%}{{FormatValue(schema.type, v)}}, {%endfor%}] 87 | if {{varName}} not in acceptable_values: 88 | raise ValueError("Value '{}' is not in the list of acceptable value: {}".format({{varName}}, acceptable_values)) 89 | {%-endif%} {#enum#} 90 | {%-if schema.const is defined%} 91 | if {{varName}} != {{schema.const | quotestring(schema.type)}}: 92 | raise ValueError("Value '{}' is not the constant value '{{schema.const}}'".format({{varName}})) 93 | {%-endif%} {#enum#} 94 | {%-if schema.pattern is defined%} 95 | pattern = r"{{schema.pattern}}" 96 | if re.match(pattern, {{varName}}) is None: 97 | raise ValueError("Value '{}' did not match the established regular expression".format({{varName}})) 98 | {%-endif%} 99 | {%-endmacro-%} 100 | 101 | {%-set fullClassPath -%} 102 | {%-if parentClass is not defined or parentClass is none-%} 103 | {{Name}} 104 | {%-else-%} 105 | {{parentClass}}.{{Name}} 106 | {%-endif-%} 107 | {%-endset-%} 108 | 109 | class {{Name}}(object): 110 | """ This class is a schema-validating wrapper around a {{schema.type}}. 111 | """ 112 | 113 | def __init__(self, value{%if schema.default is defined%}={{FormatValue(schema.type, schema.default)}}{%endif%}): 114 | self.Set(value) 115 | 116 | @staticmethod 117 | def _Validate(value): 118 | """Ensures that the provided {{schema.type}} value meets all the schema constraints. 119 | """ 120 | {{-ValidateValue(schema, 'value') | indent(8)}} 121 | 122 | def Set(self, new_value) -> {{fullClassPath}}: 123 | if isinstance(new_value, type(self)): 124 | self._value = new_value._value 125 | elif isinstance(new_value, {{ToPythonType(schema.type)}}): 126 | self._Validate(new_value) 127 | self._value = new_value 128 | {%if schema.type == 'number' -%} 129 | elif isinstance(new_value, int): 130 | self._Validate(float(new_value)) 131 | self._value = float(new_value) 132 | {%endif-%} 133 | else: 134 | raise TypeError("The provided type was not a {{fullClassPath}} or a {{ToPythonType(schema.type)}}") 135 | return self 136 | 137 | def Get(self) -> {{ToPythonType(schema.type)}}: 138 | return self._value 139 | 140 | def Serializable(self) -> {{ToPythonType(schema.type)}}: 141 | return self.Get() 142 | 143 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/reference.py.jinja2: -------------------------------------------------------------------------------- 1 | {{resolver.py_include_statement(schema['$ref'])}} 2 | {%-set itemType = resolver.py_class_name(schema['$ref'])%} 3 | 4 | class {{Name}} ({{itemType}}): 5 | pass 6 | 7 | -------------------------------------------------------------------------------- /jsonschemacodegen/templates/python/test.py.jinja2: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | {{resolver.py_include_statement(path)}} 4 | 5 | class Test{{Name|UpperCamelCase}}{{objType|UpperCamelCase}}Structure(unittest.TestCase): 6 | """A test for the following schema: 7 | {{schema}} 8 | """ 9 | 10 | def do_{{Name|snake_case}}_load(self, json_example_string): 11 | ex = json.loads(json_example_string) 12 | {{resolver.py_class_name(path)}}(ex) 13 | 14 | def test_parse_json(self): 15 | examples = [{%for example in examples%} 16 | ## Example Index {{loop.index - 1}} 17 | '{{example}}', 18 | {%endfor%}] 19 | for i, ex_str in enumerate(examples): 20 | with self.subTest(index=i): 21 | self.do_{{Name|snake_case}}_load(ex_str) 22 | 23 | def test_fail_when_not_json(self): 24 | example = 'this string here should cause it to fail{{examples[-1]}}' 25 | with self.assertRaises(ValueError): 26 | self.do_{{Name|snake_case}}_load(example) 27 | {# #} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | stringcase 2 | jacobs-jinja-too>=0.0.4 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | with open("requirements.txt", "r") as fp: 7 | requirements = fp.read().split('\n') 8 | 9 | import jsonschemacodegen._version as _version 10 | 11 | setup(name='json-schema-codegen', 12 | version=_version.__version__, 13 | url='http://github.com/pearmaster/json-schema-codegen', 14 | author='Jacob Brunson', 15 | author_email='pypi@jacobbrunson.com', 16 | description="Generate C++ or Python structures from JSON-Schema", 17 | long_description=long_description, 18 | long_description_content_type="text/markdown", 19 | license='GPLv2', 20 | packages=[ 21 | 'jsonschemacodegen', 22 | 'jsonschemacodegen.templates.cpp', 23 | 'jsonschemacodegen.templates.python', 24 | 'jsonschemacodegen.templates.markdown', 25 | ], 26 | package_data={ 27 | 'jsonschemacodegen.templates.cpp': ['*.jinja2'], 28 | 'jsonschemacodegen.templates.python': ['*.jinja2'], 29 | 'jsonschemacodegen.templates.markdown': ['*.jinja2'], 30 | }, 31 | zip_safe=False, 32 | install_requires=requirements, 33 | include_package_data=True, 34 | python_requires='>=3.7', 35 | ) --------------------------------------------------------------------------------