├── .abapgit.xml ├── CONTRIBUTING.md ├── CONTRIBUTING_USING_GENAI.md ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── README.md ├── REUSE.toml ├── docs ├── advanced.md ├── basic.md ├── class-extension.md ├── data-access.md ├── faq.md ├── history.md └── images │ ├── generate_sbook1.png │ ├── generate_sbook2.png │ └── generate_sbook_optimized.png └── src ├── package.devc.xml ├── z_ui2_data_access.clas.abap ├── z_ui2_data_access.clas.macros.abap ├── z_ui2_data_access.clas.testclasses.abap ├── z_ui2_data_access.clas.xml ├── z_ui2_json.clas.abap ├── z_ui2_json.clas.locals_imp.abap ├── z_ui2_json.clas.macros.abap ├── z_ui2_json.clas.testclasses.abap └── z_ui2_json.clas.xml /.abapgit.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | E 6 | /src/ 7 | PREFIX 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to an SAP Open Source Project 2 | 3 | ## General Remarks 4 | 5 | You are welcome to contribute content (code, documentation etc.) to this open source project. 6 | 7 | There are some important things to know: 8 | 9 | 1. You must **comply to the license of this project**, **accept the Developer Certificate of Origin** (see below) before being able to contribute. The acknowledgement to the DCO will usually be requested from you as part of your first pull request to this project. 10 | 2. Please **adhere to our [Code of Conduct](CODE_OF_CONDUCT.md)**. 11 | 3. If you plan to use **generative AI for your contribution**, please see our guideline below. 12 | 4. **Not all proposed contributions can be accepted**. Some features may fit another project better or doesn't fit the general direction of this project. Of course, this doesn't apply to most bug fixes, but a major feature implementation for instance needs to be discussed with one of the maintainers first. Possibly, one who touched the related code or module recently. The more effort you invest, the better you should clarify in advance whether the contribution will match the project's direction. The best way would be to just open an issue to discuss the feature you plan to implement (make it clear that you intend to contribute). We will then forward the proposal to the respective code owner. This avoids disappointment. 13 | 14 | ## Developer Certificate of Origin (DCO) 15 | 16 | Contributors will be asked to accept a DCO before they submit the first pull request to this projects, this happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). 17 | 18 | ## Contributing with AI-generated code 19 | 20 | As artificial intelligence evolves, AI-generated code is becoming valuable for many software projects, including open-source initiatives. While we recognize the potential benefits of incorporating AI-generated content into our open-source projects there a certain requirements that need to be reflected and adhered to when making contributions. 21 | 22 | Please see our [guideline for AI-generated code contributions to SAP Open Source Software Projects](CONTRIBUTING_USING_GENAI.md) for these requirements. 23 | 24 | ## How to Contribute 25 | 26 | 1. Make sure the change is welcome (see [General Remarks](#general-remarks)). 27 | 2. Create a branch by forking the repository and apply your change. 28 | 3. Commit and push your change on that branch. 29 | 4. Create a pull request in the repository using this branch. 30 | 5. Follow the link posted by the CLA assistant to your pull request and accept it, as described above. 31 | 6. Wait for our code review and approval, possibly enhancing your change on request. 32 | - Note that the maintainers have many duties. So, depending on the required effort for reviewing, testing, and clarification, this may take a while. 33 | 7. Once the change has been approved and merged, we will inform you in a comment. 34 | 8. Celebrate! 35 | -------------------------------------------------------------------------------- /CONTRIBUTING_USING_GENAI.md: -------------------------------------------------------------------------------- 1 | # Guideline for AI-generated code contributions to SAP Open Source Software Projects 2 | 3 | As artificial intelligence evolves, AI-generated code is becoming valuable for many software projects, including open-source initiatives. While we recognize the potential benefits of incorporating AI-generated content into our open-source projects there a certain requirements that need to be reflected and adhered to when making contributions. 4 | 5 | When using AI-generated code contributions in OSS Projects, their usage needs to align with Open-Source Software values and legal requirements. We have established these essential guidelines to help contributors navigate the complexities of using AI tools while maintaining compliance with open-source licenses and the broader [Open-Source Definition](https://opensource.org/osd). 6 | 7 | AI-generated code or content can be contributed to SAP Open Source Software projects if the following conditions are met: 8 | 9 | 1. **Compliance with AI Tool Terms and Conditions**: Contributors must ensure that the AI tool's terms and conditions do not impose any restrictions on the tool's output that conflict with the project's open-source license or intellectual property policies. This includes ensuring that the AI-generated content adheres to the [Open-Source Definition](https://opensource.org/osd). 10 | 2. **Filtering Similar Suggestions**: Contributors must use features provided by AI tools to suppress responses that are similar to third-party materials or flag similarities. We only accept contributions from AI tools with such filtering options. If the AI tool flags any similarities, contributors must review and ensure compliance with the licensing terms of such materials before including them in the project. 11 | 3. **Management of Third-Party Materials**: If the AI tool's output includes pre-existing copyrighted materials, including open-source code authored or owned by third parties, contributors must verify that they have the necessary permissions from the original owners. This typically involves ensuring that there is an open-source license or public domain declaration that is compatible with the project's licensing policies. Contributors must also provide appropriate notice and attribution for these third-party materials, along with relevant information about the applicable license terms. 12 | 4. **Employer Policies Compliance**: If AI-generated content is contributed in the context of employment, contributors must also adhere to their employer’s policies. This ensures that all contributions are made with proper authorization and respect for relevant corporate guidelines. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 10 | 11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 | 13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 | 15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 | 17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 | 19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 20 | 21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 22 | 23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 24 | 25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 26 | 27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 28 | 29 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 | 31 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 32 | 33 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 34 | 35 | (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and 36 | 37 | (b) You must cause any modified files to carry prominent notices stating that You changed the files; and 38 | 39 | (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | 41 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 42 | 43 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS 56 | 57 | APPENDIX: How to apply the Apache License to your work. 58 | 59 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 60 | 61 | Copyright [yyyy] [name of copyright owner] 62 | 63 | Licensed under the Apache License, Version 2.0 (the "License"); 64 | you may not use this file except in compliance with the License. 65 | You may obtain a copy of the License at 66 | 67 | http://www.apache.org/licenses/LICENSE-2.0 68 | 69 | Unless required by applicable law or agreed to in writing, software 70 | distributed under the License is distributed on an "AS IS" BASIS, 71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 72 | See the License for the specific language governing permissions and 73 | limitations under the License. 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ABAP to JSON serializer and deserializer 2 | 3 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP/abap-to-json)](https://api.reuse.software/info/github.com/SAP/abap-to-json) 4 | 5 | ## About this project 6 | 7 | This is an Open-source version of the standard /UI2/CL_JSON class, and its public documentation was previously available on the SCN wiki as "One more ABAP to JSON Serializer and Deserializer" in 2013. 8 | The official delivery of /UI2/CL_JSON will continue. The Open Source version (Z_UI2_JSON) is your way to contribute and get updates sooner using ABAP Git infrastructure. 9 | 10 | ### Why 11 | There are a lot of other implementations of the **ABAP to JSON Serializer and Deserializer** in SDN. Still, I found all implementations unsuitable for my needs for different reasons. From SAP_BASIS 7.40 there is also **a simple transformation** available for converting **ABAP to JSON** and **JSON to ABAP**. It is the best choice if you need maximal performance and do not care about **serialization format**, but it fits badly for properly handling ABAP types and name **pretty-printing**. 12 | 13 | So, I have written my **ABAP JSON serializer** and **ABAP JSON deserializer**, which have some key differences from other implementations. 14 | 15 | Here, you can find an Open-Source version of the standard /UI2/CL_JSON class in the form of a Z* class that you can use as a local or global one. 16 | 17 | An original and current source version can be found in class /UI2/CL_JSON delivered with UI2 Add-on (can be applied to SAP_BASIS 740 – 76X). So, you can use this ABAP JSON parser in your standard code mostly on any system. 18 | 19 | ## Alternatives 20 | If for some reason the solution does not fit your purposes, there are other alternatives you may try: 21 | * [zJSON from Uwe Fetzer (aka se38)](https://github.com/se38/zJSON) 22 | * [aJSON from Alexander Tsybulsky (aka sbcgua)](https://github.com/sbcgua/ajson) 23 | * [ABAPify JSON ( ZCL_JSON )](https://github.com/abapify/json) 24 | 25 | ## Documentation 26 | * [Base Usage](docs/basic.md) 27 | * [Advanced Usage](docs/advanced.md) 28 | * [Class Extension](docs/class-extension.md) 29 | * [Dynamic Data Accessor](docs/data-access.md) 30 | * [FAQ](docs/faq.md) 31 | * [Version History](docs/history.md) 32 | 33 | ## The Code 34 | * [Code in abapGit format](src) 35 | 36 | ## Requirements and Setup 37 | 38 | Install via [abapGit Eclipse plugin](https://github.com/abapGit/ADT_Frontend) on ABAP cloud systems and [abapGit for SAPGUI](https://docs.abapgit.org/guide-online-install.html) on systems with SAP_BASIS 7.57 or higher. 39 | 40 | ## Support, Feedback, Contributing 41 | 42 | This project is open to feature requests/suggestions, bug reports, etc. via [GitHub issues](https://github.com/SAP/abap-to-json/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, and additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). The contribution in the Open Source version will be then integrated into the standard SAP-delivered version (/UI2/CL_JSON). 43 | You can also use [Github Discussion](https://github.com/SAP/abap-to-json/discussions) to comment, request features or discuss the behavior. 44 | 45 | ## Code of Conduct 46 | 47 | Members, contributors, and leaders pledge to make participation in our community a harassment-free experience. By participating in this project, you agree to always abide by its [Code of Conduct](https://github.com/SAP/.github/blob/main/CODE_OF_CONDUCT.md). 48 | 49 | ## Licensing 50 | 51 | Copyright 2013-2023 SAP SE or an SAP affiliate company and contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/SAP/abap-to-json). 52 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "ABAP to JSON serializer and deserializer" 3 | SPDX-PackageSupplier = "ABAP 2 JSON Team " 4 | SPDX-PackageDownloadLocation = "https://github.com/SAP/abap-to-json" 5 | SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows the software to communicate with\n other software.\n API Calls to External Products are not licensed under the open-source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products, or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product or provides any third\n parties the right to use of access any SAP External Product, through API Calls." 6 | 7 | [[annotations]] 8 | path = "**" 9 | precedence = "aggregate" 10 | SPDX-FileCopyrightText = "2013-2023 SAP SE or an SAP affiliate company and abap-to-json contributors" 11 | SPDX-License-Identifier = "Apache-2.0" 12 | -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | - [CONSTRUCTOR and using instance methods](#constructor-and-using-instance-methods) 3 | - [Custom ABAP to JSON, JSON to ABAP name mapping](#custom-abap-to-json-json-to-abap-name-mapping) 4 | - [Custom formatting of values for serialization of ABAP into JSON](#custom-formatting-of-values-for-serialization-of-abap-into-json) 5 | - [Serialization/deserialization of hierarchical/recursive data](#serializationdeserialization-of-hierarchicalrecursive-data) 6 | - [Partial serialization/deserialization](#partial-serializationdeserialization) 7 | - [Deserialization of an untyped (unknown) JSON object](#deserialization-of-an-untyped-unknown-json-object) 8 | - [JSON/ABAP serialization/deserialization with runtime type information](#jsonabap-serializationdeserialization-with-runtime-type-information) 9 | - [Exception Handling in /UI2/CL_JSON](#exception-handling-in-ui2cl_json) 10 | - [JSON to ABAP transformation with the use of CALL TRANSFORMATION](#json-to-abap-transformation-with-the-use-of-call-transformation) 11 | 12 | # CONSTRUCTOR and using instance methods 13 | For simplicity, the class provides static SERIALIZE/DESERIALIZE methods that can be used without explicitly creating the class instance. However, the instance of the class is still created implicitly inside of the static method. If you have multiple usages of the class and would like to benefit from better performance and single initialization you may create the class instance manually, initialize the constructor once, and use the instance methods instead (SERIALIZE_INT/DESERIALIZE_INT/GENERATE_INT). Instance methods have fewer parameters than static ones, while the instance setting is provided in the constructor. In addition to performance gains, you also get more flexibility in class behavior customizing. The class CONSTRUCTOR has more flags as it is exposed in static methods. 14 | 15 | ## CONSTRUCTOR 16 | 17 | ### Common settings also exposed in dedicated static API 18 | * \> **COMPRESS** (bool, default = false) - Skip empty elements 19 | * \> **PRETTY_NAME** (PRETTY_NAME_MODE, default = PRETTY_MODE-NONE) - Pretty Print property names 20 | * \> **ASSOC_ARRAYS** (bool, default = false) - C_BOOL-FALSE Serialize tables with unique keys as associative array 21 | * \> **TS_AS_ISO8601** (bool, default = false) - C_BOOL-FALSE Dump timestamps as string in ISO8601 format 22 | * \> **EXPAND_INCLUDES** (bool, default = true) - Expand named includes in structures 23 | * \> **ASSOC_ARRAYS_OPT** (bool, default = false) - Optimize rendering of name-value maps 24 | * \> **NUMC_AS_STRING** (bool, default = false) - Serialize NUMC fields as strings 25 | * \> **NAME_MAPPINGS** (optional) - ABAP<->JSON Name Mapping Table 26 | * \> **CONVERSION_EXITS** (bool, default = false) - Use DDIC conversion exits on serialize/deserialize of values 27 | * \> **FORMAT_OUTPUT** (bool, default = false) - Indent and split in lines serialized JSON 28 | * \> **HEX_AS_BASE64** (bool, default = true) - Process hex values as base64 29 | * \> **GEN_OPTIMIZE** (bool, default = false) - Optimize generated structures for REF TO DATA 30 | 31 | ### Advanced settings available only with explicit constructor initialization 32 | * \> **STRICT_MODE** (bool, default = false) - Stop further processing on error 33 | * \> **BOOL_TYPES** (string, default = MC_BOOL_TYPES) - List of known boolean types 34 | * \> **BOOL_3STATE** (string, default = MC_BOOL_3STATE) - List of known 3state boolean types 35 | * \> **INITIAL_TS** (string, default = "") - Initial timestamp as JSON 36 | * \> **INITIAL_DATE** (string, default = "") - Initial date as JSON 37 | * \> **INITIAL_TIME** (string, default = "") Initial time as JSON 38 | * \> **TIME_ZONE Like** (string, default = UTC) - default time zone for conversion to date and time 39 | 40 | # Custom ABAP to JSON, JSON to ABAP name mapping 41 | By default, you control how JSON names are formatted/mapped to ABAP names by selecting proper pretty_mode as a parameter for the SERIALIZE/DESERIALIZE/GENERATE method. But sometimes, the standard, hard-coded formatting, is not enough. For example, if you need special rules for name formatting (for using special characters) or because the JSON attribute name is too long and can't be mapped to the ABAP name (which has a 30-character length limit). 42 | 43 | The recommended way for custom mapping was an extension of the class and redefining methods PRETTY_NAME or PRETTY_NAME_EX, but since note [2526405](https://launchpad.support.sap.com/#/notes/2526405) there is an easier way, without the need for its class. If you have a static list of field mappings from ABAP to JSON you can pass the name mapping table as a parameter for the constructor/serialize/deserialize and control how JSON names are formatted/mapped to ABAP names. 44 | 45 | ## ABAP to JSON name mapping example 46 | ```abap 47 | TYPES: 48 | BEGIN OF tp_s_data, 49 | sschema TYPE string, 50 | odatacontext TYPE string, 51 | shortened_abap_name TYPE string, 52 | standard TYPE string, 53 | END OF tp_s_data. 54 | 55 | DATA: ls_exp TYPE tp_s_data, 56 | lt_mapping TYPE /ui2/cl_json=>name_mappings, 57 | lv_json TYPE /ui2/cl_json=>json. 58 | 59 | lt_mapping = VALUE #( ( abap = `SSCHEMA` json = `$schema` ) 60 | ( abap = `ODATACONTEXT` json = `@odata.context` ) 61 | ( abap = `SHORTENED_ABAP_NAME` json = `VeeeeryyyyyLooooongJSONAttrbuuuuuuuuuteeeeeeeeeee` ) ). 62 | 63 | lv_json = /ui2/cl_json=>serialize( data = ls_exp name_mappings = lt_mapping ). 64 | ``` 65 | 66 | # Custom formatting of values for serialization of ABAP into JSON 67 | Sometimes you need custom formatting for your ABAP data when serializing it into JSON. In another use case, you have some custom, DDIC-defined data types, that are not automatically recognized by standard code, so no appropriate formatting is applied (for example custom boolean or timestamp type). 68 | 69 | In such cases, you have the following options: 70 | 1. Extend the class and overwrite the method DUMP_TYPE. You can check an example in the section [class extension](class-extension.md). 71 | 2. Add conversion exits for your custom type and apply formatting as part of the conversion exit. 72 | 3. Create an alternative structure, with your custom types replaced by supported types, only for serialization, and do the move of data before the serialization. 73 | 74 | # Serialization/deserialization of hierarchical/recursive data 75 | 76 | Handling the recursive data structure in ABAP is not very trivial. And it is not very trivial to serialize and deserialize it either. 77 | If you would like to model your hierarchical data (tree-like) as ABAP structures, the only allowed way will be to do it like in the example below, where you use references to generic data: 78 | ```abap 79 | TYPES: 80 | BEGIN OF ts_node, 81 | id TYPE i, 82 | children TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY, 83 | END OF ts_node. 84 | 85 | DATA: lv_exp TYPE string, 86 | lv_act TYPE string, 87 | ls_data TYPE ts_node, 88 | lr_data LIKE REF TO ls_data. 89 | 90 | ls_data-id = 1. 91 | 92 | CREATE DATA lr_data. 93 | lr_data->id = 2. 94 | APPEND lr_data TO ls_data-children. 95 | ``` 96 | Such a way is more or less straightforward and will work, but it leads to losing type information for data stored in the children's table. That means you will need to cast data when you access it. In addition, it blocks you from deserializing such data from JSON, while the parser cannot deduce the type of data that needs to be created in the children's table. But serialization will work fine: 97 | ```abap 98 | lv_exp = '{"ID":1,"CHILDREN":[{"ID":2,"CHILDREN":[]}]}'. 99 | lv_act = /ui2/cl_json=>serialize( data = ls_data ). 100 | cl_aunit_assert=>assert_equals( act = lv_act exp = lv_exp msg = 'Serialization of recursive data structure fails' ). 101 | ``` 102 | The better way to model hierarchical data in ABAP is with the help of objects. In contrast, objects are always processed as references and ABAP allows you to create nested data structures, referring to objects of the same type: 103 | ```abap 104 | CLASS lcl_test DEFINITION FINAL. 105 | PUBLIC SECTION. 106 | DATA: id TYPE i. 107 | DATA: children TYPE STANDARD TABLE OF REF TO lcl_test. 108 | ENDCLASS. "lcl_test DEFINITION 109 | ``` 110 | In that manner, you can process data in the same way as with ABAP structures but using typed access and serialization/deserialization of data in JSON works fine while types can be deduced on 111 | ```abap 112 | DATA: lo_act TYPE REF TO lcl_test, 113 | lo_exp TYPE REF TO lcl_test, 114 | lv_json TYPE string, 115 | lo_child TYPE REF TO lcl_test. 116 | 117 | CREATE OBJECT lo_exp. 118 | 119 | lo_exp ->id = 1. 120 | 121 | CREATE OBJECT lo_child. 122 | lo_child->id = 2. 123 | APPEND lo_child TO lo_exp->children. 124 | 125 | lv_json = /ui2/cl_json=>serialize( data = lo_exp ). 126 | ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data = lo_act ). 127 | ``` 128 | Remark: There are some constraints for data design that exist regarding the deserialization of objects: 129 | * You cannot use constructors with obligatory parameters 130 | * References to interfaces will not be deserialized 131 | 132 | ## Serializing of protected and private attributes 133 | If you do the serialization from outside of your class, you can access only the public attributes of that class. To serialize all types of attributes (private+protected) you need to allow the JSON serializer class to access them. This can be done by defining the serializer class as a friend of your class. In this way, you do not disrupt your encapsulation for other classes but enable the serializer class to access all data of your class. 134 | 135 | If you do not own a class you want to serialize, you can inherit it from your class and add friends there. In this case, you can access at least protected attributes. 136 | 137 | # Partial serialization/deserialization 138 | When it is needed: 139 | * You deserialize JSON to ABAP but would like some known parts to be deserialized as JSON string, while you do not know the nesting JSON structure. 140 | * You deserialize a collection (array/associative array) having objects with heterogeneous structures (for example the same field has a different type depending on object type). Using partial deserialization, you can restore a type such as a JSON string in ABAP, and apply additional deserialization based on the object type later. 141 | * You serialize ABAP to JSON and have some ready JSON pieces (strings) you want to mix in. 142 | 143 | The solution /UI2/CL_JSON has for this type /UI2/CL_JSON=>JSON (alias for built-in type string). ABAP fields declared with this type will be serialized/deserialized as JSON pieces. During serialization from ABAP to JSON, the content of such JSON piece is not validated for correctness, so if you pass an invalid JSON block, it may destroy the complete resulting JSON string at the end. 144 | 145 | I've included examples of partial serialization/deserialization below. 146 | 147 | Serialization: 148 | ```abap 149 | TYPES: BEGIN OF ts_record, 150 | id TYPE string, 151 | columns TYPE /ui2/cl_json=>json, 152 | END OF ts_record. 153 | 154 | DATA: lv_json TYPE /ui2/cl_json=>json, 155 | lt_data TYPE SORTED TABLE OF ts_record WITH UNIQUE KEY id, 156 | ls_data LIKE LINE OF lt_data. 157 | 158 | ls_data-id = 'O000001ZZ_SO_GRES_CONTACTS'. 159 | ls_data-columns = '{"AGE":{"bVisible":true,"iPosition":2},"BRSCH":{"bVisible":true}}'. 160 | INSERT ls_data INTO TABLE lt_data. 161 | 162 | ls_data-id = 'O000001ZZ_TRANSIENT_TEST_A'. 163 | ls_data-columns = '{"ABTNR":{"bVisible":false},"CITY1":{"bVisible":false},"IC_COMPANY_KEY":{"bVisible":true}}'. 164 | INSERT ls_data INTO TABLE lt_data. 165 | 166 | lv_json = /ui2/cl_json=>serialize( data = lt_data assoc_arrays = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). 167 | 168 | WRITE / lv_json. 169 | ``` 170 | Results in: 171 | ```json 172 | { 173 | "O000001ZZ_SO_GRES_CONTACTS": { 174 | "columns": { 175 | "AGE": { 176 | "bVisible": true, 177 | "iPosition": 2 178 | }, 179 | "BRSCH": { 180 | "bVisible": true 181 | } 182 | } 183 | }, 184 | "O000001ZZ_TRANSIENT_TEST_A": { 185 | "columns": { 186 | "ABTNR": { 187 | "bVisible": false 188 | }, 189 | "CITY1": { 190 | "bVisible": false 191 | }, 192 | "IC_COMPANY_KEY": { 193 | "bVisible": true 194 | } 195 | } 196 | } 197 | } 198 | ``` 199 | Deserialization: 200 | ```abap 201 | TYPES: BEGIN OF ts_record, 202 | id TYPE string, 203 | columns TYPE /ui2/cl_json=>json, 204 | END OF ts_record. 205 | 206 | DATA: lv_json TYPE string, 207 | lt_act TYPE SORTED TABLE OF ts_record WITH UNIQUE KEY id. 208 | 209 | CONCATENATE 210 | '{"O000001ZZ_SO_GRES_CONTACTS":{"columns":{"AGE":{"bVisible":true,"iPosition":2},"BRSCH":{"bVisible":true}}},' 211 | '"O000001ZZ_TRANSIENT_TEST_A":{"columns":{"ABTNR":{"bVisible":false},"CITY1":{"bVisible":false},"IC_COMPANY_KEY":{"bVisible":true}}}}' 212 | INTO lv_json. 213 | 214 | " If you know the first level of the underlying structure ("columns" field) -> Output Var 1 215 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json assoc_arrays = abap_true CHANGING data = lt_act ). 216 | 217 | " if you do not know the underlying structure of the first level (naming of the second field e.g columns in the example does not matter ) 218 | " => result is a little bit different -> Output Var 2 219 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json assoc_arrays = abap_true assoc_arrays_opt = abap_true CHANGING data = lt_act ). 220 | ``` 221 | Results in the following ABAP data: 222 | ## ABAP Output (variant 1) 223 | ``` 224 | ID(CString) COLUMNS(CString) 225 | O000001ZZ_SO_GRES_CONTACTS {"AGE":{"bVisible":true,"iPosition":2},"BRSCH":{"bVisible":true}} 226 | O000001ZZ_TRANSIENT_TEST_A {"ABTNR":{"bVisible":false},"CITY1":{"bVisible":false},"IC_COMPANY_KEY":{"bVisible":true}} 227 | ``` 228 | ## ABAP Output (variant 2) 229 | ``` 230 | ID(CString) COLUMNS(CString) 231 | O000001ZZ_SO_GRES_CONTACTS {"columns":{"AGE":{"bVisible":true,"iPosition":2},"BRSCH":{"bVisible":true}}} 232 | O000001ZZ_TRANSIENT_TEST_A {"columns":{"ABTNR":{"bVisible":false},"CITY1":{"bVisible":false},"IC_COMPANY_KEY":{"bVisible":true}}} 233 | ``` 234 | 235 | # Deserialization of an untyped (unknown) JSON object 236 | Suppose you need to deserialize a JSON object with an unknown structure, or you do not have a passing data type on the ABAP side, or the data type of the resulting object may vary. In that case, you can generate an ABAP object on the fly, using the corresponding GENERATE method. The method has some limitations compared to standard deserialization: 237 | * Fields are generated as a reference (even elementary types). If you want a more user-friendly generation - use the optimization flag (see below). 238 | * you can not control how deserialized arrays or timestamps 239 | * you can not access components of generated structure statically (while the structure is unknown at compile time) and need to use dynamic access 240 | * you need to accept default logic for type detection. Supported types are int, float, packaged, strings, boolean, date, time, and timestamps. 241 | 242 | The simplest example, with straightforward access: 243 | ```abap 244 | DATA: lv_json TYPE /ui2/cl_json=>json, 245 | lr_data TYPE REF TO data. 246 | 247 | FIELD-SYMBOLS: 248 | TYPE data, 249 | TYPE any, 250 | TYPE any. 251 | 252 | lv_json = `{"name":"Key1","properties":{"field1":"Value1","field2":"Value2"}}`. 253 | lr_data = /ui2/cl_json=>generate( json = lv_json ). 254 | 255 | " OK, generated, now let us access some field :( 256 | IF lr_data IS BOUND. 257 | ASSIGN lr_data->* TO . 258 | ASSIGN COMPONENT `PROPERTIES` OF STRUCTURE TO . 259 | IF IS ASSIGNED. 260 | lr_data = . 261 | ASSIGN lr_data->* TO . 262 | ASSIGN COMPONENT `FIELD1` OF STRUCTURE TO . 263 | IF IS ASSIGNED. 264 | lr_data = . 265 | ASSIGN lr_data->* TO . 266 | WRITE: . " We got it -> Value1 267 | ENDIF. 268 | ENDIF. 269 | ENDIF. 270 | ``` 271 | A nice alternative, using [dynamic data accessor helper class](data-access.md): 272 | ```abap 273 | DATA: lv_json TYPE /ui2/cl_json=>json, 274 | lr_data TYPE REF TO data, 275 | lv_val TYPE string. 276 | 277 | lv_json = `{"name":"Key1","properties":{"field1":"Value1","field2":"Value2"}}`. 278 | lr_data = /ui2/cl_json=>generate( json = lv_json ). 279 | 280 | /ui2/cl_data_access=>create( ir_data = lr_data iv_component = `properties-field1`)->value( IMPORTING ev_data = lv_val ). 281 | WRITE: lv_val. 282 | ``` 283 | 284 | ## Optimized type generation 285 | From PL19 it is possible to use the new switch for GENERATE (optimize) and DESERIALIZE (gen_optimize) to activate the optimization of generated ABAP data for REF TO DATA (fewer references, easily readable and accessible). But it results in longer processing for data generation (~25%). 286 | 287 | The generation without optimization flag: 288 | 289 | ![generate_sbook1](images/generate_sbook1.png) 290 | ![generate_sbook2](images/generate_sbook2.png) 291 | 292 | With optimization flag: 293 | ![generate_sbook_optimized](images/generate_sbook_optimized.png) 294 | 295 | ## Implicit generation of ABAP objects on deserialization 296 | 297 | In addition to the explicit generation of the ABAP data objects from the JSON string, the deserializer supports an implicit way of generation, during DESERIALIZE(INT) call. To trigger generation, your output data structure shall contain a field with the type REF TO DATA, and the field name shall match the JSON attribute (pretty name rules are considered). Depending on the value of the field, the behavior may differ: 298 | * The value is not bound (initial): deserialize will use generation rules when creating corresponding data types of the referenced value 299 | * The value is bound (but may be empty): the deserializer will create a new referenced value based on the referenced type. 300 | 301 | ### Example of implicit generation of ABAP data from JSON string 302 | ```abap 303 | TYPES: 304 | BEGIN OF ts_dyn_data1, 305 | name TYPE string, 306 | value TYPE string, 307 | END OF ts_dyn_data1, 308 | BEGIN OF ts_dyn_data2, 309 | key TYPE string, 310 | value TYPE string, 311 | END OF ts_dyn_data2, 312 | BEGIN OF ts_data, 313 | str TYPE string, 314 | data TYPE REF TO data, 315 | END OF ts_data. 316 | 317 | DATA: 318 | ls_data TYPE ts_data, 319 | lv_json TYPE /ui2/cl_json=>json. 320 | 321 | lv_json = `{"str":"Test","data":{"name":"name1","value":"value1"}}`. 322 | 323 | " deserialize data and use generic generation for field "data", 324 | " the same as with method GENERATE (using temporary data type) 325 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data = ls_data ). 326 | 327 | " deserialize data and use type TS_DYN_DATA1 for the field "data" 328 | CREATE DATA ls_data-data TYPE ts_dyn_data1. 329 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data = ls_data ). 330 | 331 | " deserialize data and use alternative type TS_DYN_DATA2 for the field "data" 332 | CREATE DATA ls_data-data TYPE ts_dyn_data2. 333 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data = ls_data ). 334 | ``` 335 | 336 | # JSON/ABAP serialization/deserialization with runtime type information 337 | Automatic deserialization of the JSON into the appropriate ABAP structure is not supported. The default implementation assumes that you need to know the target data structure (or at least partial structure, it will also work) to deserialize JSON in ABAP and then work with typed data. 338 | 339 | But if for some reason one needs the ability to deserialize JSON in source ABAP structure in a generic way, he can extend both serialize/deserialize methods and wrap outputs/inputs of /UI2/CL_JSON data by technical metadata describing source ABAP structure and use this information during deserialization (or use GENERATE method). Of course, you must ensure that the source ABAP data type is known in the deserialization scope (global and local types are "visible"). 340 | 341 | See the example below: 342 | ```abap 343 | TYPES: BEGIN OF ts_json_meta, 344 | abap_type LIKE cl_abap_typedescr=>absolute_name, 345 | data TYPE string, 346 | END OF ts_json_meta. 347 | 348 | DATA: lt_flight TYPE STANDARD TABLE OF sflight, 349 | lv_json TYPE string, 350 | lo_data TYPE REF TO data, 351 | ls_json TYPE ts_json_meta. 352 | 353 | FIELD-SYMBOLS: TYPE any. 354 | 355 | SELECT * FROM sflight INTO TABLE lt_flight. 356 | 357 | * serialize table lt_flight into JSON, skipping initial fields and converting ABAP field names into camelCase 358 | ls_json-data = /ui2/cl_json=>serialize( data = lt_flight compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). 359 | ls_json-abap_type = cl_abap_typedescr=>describe_by_data( lt_flight )->absolute_name. 360 | lv_json = /ui2/cl_json=>serialize( data = ls_json compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). 361 | WRITE / lv_json. 362 | 363 | CLEAR: ls_json, lt_flight. 364 | 365 | * deserialize JSON string json into internal table lt_flight doing camelCase to ABAP like field name mapping 366 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = ls_json ). 367 | CREATE DATA lo_data TYPE (ls_json-abap_type). 368 | ASSIGN lo_data->* TO . 369 | /ui2/cl_json=>deserialize( EXPORTING json = ls_json-data pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = ). 370 | 371 | IF lo_data IS NOT INITIAL. 372 | BREAK-POINT. " check here lo_data 373 | ENDIF. 374 | ``` 375 | 376 | # Exception Handling in /UI2/CL_JSON 377 | By default, /UI2/CL_JSON tries to hide from consumer-thrown exceptions (that may happen during deserialization) catching them at all levels. In some cases, it will result in missing attributes, in other cases, when an error is critical and the parser can not restore, you will get an empty object back. The main TRY/CATCH block prevents exceptions from the DESERIALIZE method. 378 | 379 | If you want to get a report in case of error, use the instance method DESERIALIZE_INT which may fire CX_SY_MOVE_CAST_ERROR. The reporting is rather limited - all errors are translated into CX_SY_MOVE_CAST_ERROR and no additional information is available. But from PL19 you will also get extra details reported in the target (ABAP) and source (JSON) fields, as the type of ABAP field, that is not filled and the JSON node leading to the error. More details can be found in [this issue](https://github.com/SAP/abap-to-json/pull/8). 380 | 381 | # JSON to ABAP transformation with the use of CALL TRANSFORMATION 382 | Below is a small example of CALL TRANSFORMATION usage to produce JSON from ABAP structures. Don't ask me for details - I do not know them. (smile) It was just a small test of me. 383 | ```abap 384 | DATA: lt_flight TYPE STANDARD TABLE OF sflight. 385 | SELECT * FROM sflight INTO TABLE lt_flight. 386 | 387 | * ABAP to JSON 388 | DATA(lo_writer) = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ). 389 | CALL TRANSFORMATION id SOURCE text = lt_flight RESULT XML lo_writer. 390 | DATA(lv_jsonx) = lo_writer->get_output( ). 391 | DATA(lv_json) = /ui2/cl_json=>raw_to_string( lv_jsonx ). 392 | 393 | * JSON to ABAP 394 | CALL TRANSFORMATION id SOURCE XML lv_jsonx RESULT text = lt_flight. 395 | ``` 396 | The transformation above uses the built-in ID transformation, that produces "ABAP JSON" output - that may be ok for you if you own the sender and receiver side and need the best performance. If you want your custom transformation rules you can use [this project](https://github.com/timostark/abap-json-serialization) to create an XSLT transformation for your data structure. 397 | If transformation for some reason does not work, please assist with the following notes: [1650141](http://service.sap.com/sap/support/notes/2368774) and [1648418](http://service.sap.com/sap/support/notes/2368774). 398 | See also the [blog of Horst Keller](http://scn.sap.com/community/abap/blog/2013/07/04/abap-news-for-release-740--abap-and-json) for more details. 399 | 400 | # Continue reading 401 | * [Basic usage of the class](basic.md) 402 | * [Extension (inheriting) of the class](class-extension.md) 403 | * [FAQ](faq.md) 404 | * [Version History](history.md) 405 | -------------------------------------------------------------------------------- /docs/basic.md: -------------------------------------------------------------------------------- 1 | # Basic Usage 2 | 3 | # What the class can 4 | ## ABAP to JSON 5 | * Serialize classes, structures, internal tables, class and data references, and elementary types. Complex types, such as a table of structures/classes, classes with complex attributes, etc. are also supported and recursively processed. 6 | * **ABAP to JavaScript** adopted way of data type serializations: 7 | * strings, character types to JavaScript string format (no length limitation), 8 | * ABAP_BOOL / BOOLEAN / XFELD / BOOLE_D to JavaScript Boolean, 9 | * Built-in TRIBOOL (TRUE/FALSE/UNDEFINED = 'X'/'-'/'') support, for better control of initial values when serializing into JavaScript Boolean 10 | * int/floats/numeric/packed to JavaScript Integers/floats, 11 | * date/time to JavaScript date/time string representation as "2015-03-24" or "15:30:48", 12 | * timestamp to JavaScript integer or ISO8601 string 13 | * structures to JavaScript objects (include types are also supported; aliases => AS are ignored) 14 | * **convert ABAP internal table to JSON**, e.g JavaScript arrays or associative arrays (objects) 15 | * Support of conversion exits on ABAP data serialization 16 | * Pretty Printing of JavaScript property names: MY_DATA -> myData, /SAPAPO/MY_DATA -> sapapoMyData. 17 | * Condensing of default values: initial values are not rendered into the resulting JSON string 18 | * Optionally apply **JSON formatting/beautifing/pretty-print** for serialized JSON 19 | * Performance is optimized for processing big internal tables with structures 20 | 21 | ## JSON to ABAP 22 | * Deserialize JSON objects, arrays, and elementary types into corresponding ABAP structures. Complex objects, with embedded arrays and objects with any level of nesting, are also supported. 23 | * **Convert JSON to an internal table** 24 | * Generic deserialization of JSON objects into reference data types: 25 | * as simple data types (integer, boolean, or string into generic data reference (REF TO DATA) -> ABAP type is selected based on JSON type. 26 | * as dynamically generated complex object (structures, tables, mixed) for initial REF TO DATA fields 27 | * as typed references for prefilled REF TO DATA fields (you assign a reference to a typed empty data object to the REF TO DATA field in execution time) 28 | * Deserialization of unknown JSON structures possible using method GENERATE into on-the-fly created data types 29 | * On **JSON** to **ABAP** transformation following rules are used: 30 | * objects parsed into corresponding ABAP structures, classes (only classes with constructors with no obligatory parameters are supported), or internal hash/sorted tables 31 | * arrays converted to internal tables (complex tables are also supported). 32 | * Boolean converted as ABAP_BOOL (‘’ or ‘X’) 33 | * Date/Time/Timestamps from JSON converted based on the type of corresponding ABAP element 34 | * integers/floats/strings moved to corresponding fields using ABAP move semantic (strings are un-escaped). There is no limit on the size of deserialized strings, the only restriction is the constraints of receiving data type. Escaped Unicode symbols (\u001F) in strings are decoded. 35 | * elementary data types are converted if they do not match: JavaScript integer can come into ABAP string or JavaScript string into ABAP integer, etc. 36 | * Transformation considers property naming guidelines for **JSON** and **ABAP** so that camelCase names will be copied into the corresponding CAMEL_CASE field if the CAMELCASE field is not found in the ABAP structure. Please don't forget to use the same PRETTY_MODE for deserialization, as you have used for serialization. 37 | * Default field values, specified in reference ABAP variable are preserved, and not overwritten if not found in the JSON object 38 | * Transformation of **JSON** structures into ABAP class instances is NOT supported. 39 | * Support of conversion exits on deserialization 40 | 41 | The parser for serializing/deserializing uses single-pass parsing and is optimized to provide the best possible performance in ABAP in a release-independent way. But for time-critical applications, which have kernel version 7.20 and higher, it is recommended to use built-in [**JSON** to **ABAP** transformations (CALL TRANSFORMATION)](advanced.md#json-to-abap-transformation-with-the-use-of-call-transformation). 42 | 43 | # Usage example 44 | ## ABAP to JSON usage example 45 | ```abap 46 | CLASS demo DEFINITION. 47 | PUBLIC SECTION. 48 | CLASS-METHODS main. 49 | ENDCLASS. 50 | 51 | CLASS demo IMPLEMENTATION. 52 | METHOD main. 53 | 54 | DATA: lt_flight TYPE STANDARD TABLE OF sflight, 55 | lrf_descr TYPE REF TO cl_abap_typedescr, 56 | lv_json TYPE /ui2/cl_json=>json. 57 | 58 | 59 | SELECT * FROM sflight INTO TABLE lt_flight. 60 | 61 | " serialize table lt_flight into JSON, skipping initial fields and converting ABAP field names into camelCase 62 | lv_json = /ui2/cl_json=>serialize( data = lt_flight 63 | pretty_name = /ui2/cl_json=>pretty_mode-camel_case 64 | compress = abap_true 65 | ). 66 | 67 | cl_demo_output=>write_json( lv_json ). 68 | 69 | CLEAR lt_flight. 70 | 71 | " deserialize JSON string json into internal table lt_flight doing camelCase to ABAP like field name mapping 72 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json pretty_name = /ui2/cl_json=>pretty_mode-camel_case 73 | CHANGING data = lt_flight ). 74 | 75 | " serialize ABAP object into JSON string 76 | lrf_descr = cl_abap_typedescr=>describe_by_data( lt_flight ). 77 | lv_json = /ui2/cl_json=>serialize( data = lrf_descr format_output = abap_true ). 78 | 79 | cl_demo_output=>write_json( lv_json ). 80 | 81 | cl_demo_output=>display( ). 82 | 83 | ENDMETHOD. 84 | ENDCLASS. 85 | 86 | START-OF-SELECTION. 87 | demo=>main( ). 88 | ``` 89 | 90 | ## JSON Output 1 91 | ```json 92 | [ 93 | { 94 | "mandt": "120", 95 | "carrid":"AA", 96 | "connid":17, 97 | "fldate":"2018-08-15", 98 | "price":422.94, 99 | "currency":"USD", 100 | "planetype":"747-400", 101 | "seatsmax":385, 102 | "seatsocc":268, 103 | "paymentsum":192361.84 104 | }, 105 | { 106 | "mandt":"120", 107 | "carrid":"AA", 108 | "connid":17 109 | }, 110 | ... 111 | ``` 112 | ```json 113 | { 114 | "ABSOLUTE_NAME":"\\TYPE=%_T00004S00000000O0000014656", 115 | "DECIMALS":0, 116 | "HAS_UNIQUE_KEY":false, 117 | "INITIAL_SIZE":0, 118 | "KEY": 119 | [ 120 | { 121 | "NAME":"MANDT" 122 | }, 123 | { 124 | "NAME":"CARRID" 125 | }, 126 | { 127 | "NAME":"CONNID" 128 | } 129 | .... 130 | ``` 131 | # API description 132 | Two static methods are most interesting in common cases: SERIALIZE and DESERIALIZE. The rest of the public methods are defined as public only for reuse purposes if you want to build/extend your own serialization/deserialization code. 133 | 134 | ## SERIALIZE: Serialize ABAP object into JSON 135 | 136 | * \> **DATA** (any) - any ABAP object/structure/table/element to be serialized 137 | * \> **COMPRESS** (bool, default = false) - tells serializer to skip empty elements/objects during serialization. So, all of which IS INITIAL = TRUE. 138 | * \> **NAME** (string, optional) - optional name of the serialized object. Will '"name" : {...}' instead of ' {...} ' if supplied. 139 | * \> **PRETTY_NAME** (enum, optional)- mode controls how ABAP field names are transformed into JSON attribute names. More can be found in the description below. 140 | * \> **TYPE_DESCR** (ref to CL_ABAP_TYPEDESCR, optional) - if you already know the object type, pass it to improve performance. 141 | * \> **ASSOC_ARRAYS** (bool, default = false) - controls how to serialize hash or sorted tables with unique keys. More can be found in the description below. 142 | * \> **ASSOC_ARRAYS_OPT** (bool, default = false) - when set, the serializer will optimize the rendering of name-value associated arrays (hash maps) in JSON 143 | * \> **TS_AS_ISO8601** (bool, default = false) - says serializer to output timestamps using ISO8601 format. 144 | * \> **NUMC_AS_STRING** (bool, default = false) - Controls how NUMC fields are serialized. If set to ABAP_TRUE, NUMC fields are serialized not as integers, but as strings, with all leading zeros. Deserialization works compatible with both ways of NUMC serialized data. 145 | * \> **NAME_MAPPINGS** (table) - ABAP<->JSON Name Mapping Table 146 | * \> **CONVERSION_EXITS** (bool, default = false) - use DDIC conversion exits on serialize of values (performance loss!) 147 | * \> **FORMAT_OUTPUT** (bool, default = false) - Indent, add formatting spaces, and split into lines serialized JSON 148 | * \> **HEX_AS_BASE64** (bool, default = true) - Serialize hex values as base64 149 | * \< **R_JSON** - output JSON string. 150 | 151 | ## DESERIALIZE: Deserialize ABAP object from JSON string 152 | 153 | * \> **JSON** (string) - input JSON object string to deserialize 154 | * \> **JSONX** (xstring) - input JSON object as a raw string to deserialize 155 | * \> **PRETTY_NAME** (enum, optional) - mode, controlling how JSON field names are mapped to ABAP component names. More can be found in the description below. 156 | * \> **ASSOC_ARRAYS** (bool, default = false) - controls how to deserialize JSON objects into hash or sorted tables with unique keys. More can be found in the description below. 157 | * \> **ASSOC_ARRAYS_OPT** (bool, default = false) - when set, the deserializer will take into account the optimized rendering of associated arrays (properties) in JSON. 158 | * \> **NAME_MAPPINGS** (table) - ABAP<->JSON Name Mapping Table 159 | * \> **CONVERSION_EXITS** (bool, default = false) - use DDIC conversion exits on deserialize of values (performance loss!) 160 | * \> **HEX_AS_BASE64** (bool, default = true) - deserialize hex values as base64 161 | * \> **[GEN_OPTIMIZE](advanced.md#deserialization-of-an-untyped-unknown-json-object)** (bool, default = false) - optimize generated types, structures, and table types for REF TO DATA 162 | * \<\> **DATA** (any) - ABAP object/structure/table/element to be filled from JSON string. If the ABAP structure contains more fields than in the JSON object, the content of unmatched fields is preserved. 163 | 164 | ## GENERATE: Generates ABAP object from JSON 165 | 166 | * \> **JSON** (string) - input JSON object string to generate 167 | * \> **JSONX** (xstring) - input JSON object as a raw string to generate 168 | * \> **PRETTY_NAME** (enum, optional) - mode, controlling how JSON field names are mapped to ABAP component names. More can be found in the description below. 169 | * \> **[OPTIMIZE](advanced.md#deserialization-of-an-untyped-unknown-json-object)** (bool, default = false) - optimize generated types, structures, and table types for REF TO DATA 170 | * \> **NAME_MAPPINGS** (table) - ABAP<->JSON Name Mapping Table 171 | * \< **RR_DATA** (REF TO DATA) - a reference to ABAP structure/table dynamically generated from JSON string. 172 | 173 | In addition to the explained methods, there are two options, that need a wider explanation: 174 | 175 | ## PRETTY_NAME : enumeration of modes, defined as constant /UI2/CL_JSON=>pretty_name. 176 | 177 | * **NONE** - ABAP component names are serialized as is (UPPERCASE). 178 | * **LOW_CASE** - ABAP component names serialized in low case 179 | * **CAMEL_CASE** - ABAP component types serialized in CamelCase where symbol "\_" is treated as a word separator (and removed from the resulting name). 180 | * **EXTENDED** - works the same way as CAMEL_CASE but also, has extended logic for encoding special characters, such as: ".", "@", "~", etc. It shall be used if you need JSON names with characters not allowed for ABAP data component names. If you do not have special characters in JSON names, do not use it - the performance will be slower than CAMEL_CASE mode. Example: ABAP name '\_\_A\_\_SCHEMA' translates in JSON name '@schema' 181 | Encoding rules (ABAP name → JSON name): 182 | * '\_\_E\_\_' → '!' 183 | * '\_\_N\_\_' → '#' 184 | * '\_\_D\_\_' → '$' 185 | * '\_\_P\_\_' → '%' 186 | * '\_\_M\_\_' → '&' 187 | * '\_\_S\_\_' → '*' 188 | * '\_\_H\_\_' → '-' 189 | * '\_\_T\_\_' → '~' 190 | * '\_\_L\_\_' → '/' 191 | * '\_\_C\_\_' → ':' 192 | * '\_\_V\_\_' → '|' 193 | * '\_\_A\_\_' → '@' 194 | * '\_\_O\_\_' or '\_\_\_' → '.' 195 | **NONE** and **LOW_CASE** work the same way for DESERIALIZE. 196 | 197 | ## ASSOC_ARRAYS : 198 | 199 | This option controls how hashed or sorted tables with unique keys are serialized/deserialized. Normally, ABAP internal tables are serialized into JSON arrays. Still, in some cases, you will like to serialize them as associative arrays (JSON objects) where every table row shall be reflected as a separate property of the JSON object. This can be achieved by setting the *ASSOC_ARRAYS* parameter to TRUE. If set, the serializer checks for sorted/hashed tables with a UNIQUE key(s) and serialize them as an object. The JSON property name, reflecting row, constructed from values of fields, used in key separated by constant *MC_KEY_SEPARATOR* = '-'. If the table has only one field marked as key, the value of this single field becomes a property name and is REMOVED from the associated object (to eliminate redundancy). If TABLE_LINE is used as a unique key, all values of all fields construct key property names (separated by *MC_KEY_SEPARATOR*). During deserialization, logic works vice versa: if *ASSOC_ARRAYS* is set to TRUE, and the JSON object matches the internal hash or sorted table with the unique key, the object is transformed into the table, where every object property is reflected in a separated table row. If the ABAP table has only one key field, the property name is transformed into a value of this key field. 200 | 201 | ## ASSOC_ARRAYS_OPT: 202 | 203 | By default, when dumping hash/sorted tables with a unique key into JSON, the serializer will write the key field as the property name, and the rest of the fields will write the object value of properties: 204 | ```abap 205 | TYPES: BEGIN OF ts_record, 206 | key TYPE string, 207 | value TYPE string, 208 | END OF ts_record. 209 | 210 | DATA: lt_act TYPE SORTED TABLE OF ts_record WITH UNIQUE KEY key. 211 | lv_json = /ui2/cl_json=>serialize( data = lt_exp assoc_arrays = abap_true ). 212 | ``` 213 | Output JSON 214 | ```json 215 | { 216 | "KEY1": { 217 | "value": "VALUE1" 218 | }, 219 | "KEY2": { 220 | "value": "VALUE2" 221 | } 222 | } 223 | ``` 224 | But suppose you will use the assoc_arrays_opt flag during serialization. In that case, the serializer will try to omit unnecessary object nesting by dumping simple, name/value tables, containing only one key field and one value field: 225 | ```abap 226 | lv_json = /ui2/cl_json=>serialize( data = lt_exp assoc_arrays = abap_true assoc_arrays_opt = abap_true ). 227 | ``` 228 | Output JSON 229 | ```json 230 | { 231 | "KEY1": "VALUE1", 232 | "KEY2": "VALUE2" 233 | } 234 | ``` 235 | For deserialization, the flag tells the deserializer that the value shall be placed in a non-key field of the structure. 236 | 237 | 238 | Here is an example of deserialization, in which mentioned above two options can be used: 239 | ```json 240 | { 241 | "id": "509e28db-6794-4e02-91d3-e35f4c7310e9", 242 | "form": { 243 | "Authorised": "TBB ENTERPRISE SDN. BHD.", 244 | "Fax No:": "92225491", 245 | "Issued Date :": "02-10-2024", 246 | "Voluntary Excess": "0.00", 247 | "Page": "2", 248 | "Allianz General Insurance Company (Malaysia) Berhad": "(200601015674)", 249 | } 250 | } 251 | ``` 252 | Look into sub-attributes of the "form" field. They can not be easily mapped into any ABAP structure, because attribute names contain spaces, special characters, etc. The best way would be to deserialize this fragment in a table with a key, corresponding to the attribute name. The example code for deserialization then will look like this: 253 | ```abap 254 | TYPES: BEGIN OF ts_name_value, 255 | name TYPE string, 256 | value TYPE string, 257 | END OF ts_name_value, 258 | BEGIN OF ts_data, 259 | id TYPE string, 260 | form TYPE SORTED TABLE OF ts_name_value WITH UNIQUE KEY name, 261 | END OF ts_data. 262 | 263 | DATA: ls_data TYPE ts_data. 264 | /ui2/cl_json=>deserialize( EXPORTING json = lv_json 265 | assoc_array = abap_true 266 | assoc_array_opt = abap_true 267 | CHANGING data = ls_data ). 268 | ``` 269 | 270 | # Supported SAP_BASIS releases 271 | The code was tested from SAP_BASIS 7.00 and higher, but I do not see the reasons why it cannot be downported on lower releases either. But if you plan to use it on SAP_BASIS 7.02 and higher (and do not need property name pretty-printing) better consider the standard solution for **ABAP**, using [CALL TRANSFORMATION](advanced.md#json-to-abap-transformation-with-the-use-of-call-transformation). It shall be faster, while implemented in the kernel. Maybe the best will be, if you need support in lower SAP_BASIS releases as well as in 7.02 and higher, to modify the provided class in a way to generate the same **JSON** format as standard ABAP CALL TRANSFORMATION for JSON does and redirect flow to home-made code or built-in **ABAP** transformation depending on SAP_BASIS release. 272 | 273 | # Further optimizations 274 | * Be aware, that usage of flag conversion_exits may significantly decrease performance - use only in cases, when you are sure that you need it. 275 | * Escaping property values can be expensive. To optimize performance, in this case, you can replace escapement code with some kernel-implemented function (from cl_http_utility class for example), instead of explicit *REPLACE ALL OCCURRENCES* calls. 276 | * Unescaping can influence deserialization performance even worse, depending on the fact that your JSON has encoded \n\r\t\f\b\x. So, avoid their usage if you can. 277 | * It is possible to significantly increase performance for serialization/deserialization by dropping the support of releases below 7.40. That can be realized by moving base parsing from ABAP to kernel-implemented classes cl_sxml_string_writer and cl_sxml_string_reader. 278 | 279 | # Remarks 280 | Due to optimization reasons, some methods were converted to macros, to reduce overhead for calling methods for data type serialization. If performance in your case is not critical, and you prefer clean/debuggable code you can replace macro calls with corresponding methods. 281 | 282 | # Continue reading 283 | * [Advanced Use cases](advanced.md) 284 | * [Extension (inheriting) of the class](class-extension.md) 285 | * [FAQ](faq.md) 286 | * [Version History](history.md) 287 | 288 | # Related pages 289 | * [Does /UI2/CL_JSON work in ABAP Cloud?](https://answers.sap.com/questions/12699592/does-ui2cl-json-work-in-abap-cloud.html) 290 | * [Return generic result via SAP RFC (performance)](https://stackoverflow.com/questions/54037544/abap-return-generic-result-via-sap-rfc-json) 291 | * [RFC7159 - The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159) 292 | * [OData JSON Deserialize](https://answers.sap.com/questions/12998367/odata-simple-json-deserialize.html?childToView=13007205) 293 | -------------------------------------------------------------------------------- /docs/class-extension.md: -------------------------------------------------------------------------------- 1 | # /UI2/CL_JSON extension 2 | 3 | If standard class functionality does not fit your requirements there are two ways how you can adapt it to your needs: 4 | 5 | * Use a local copy of the class /UI2/CL_JSON and modify logic directly by changing the original code. 6 | * Inherit from class /UI2/CL_JSON and override methods where another logic is required. 7 | 8 | The advantage of the first approach is that you are completely free in what you may change and have full control of the class lifecycle. The disadvantage is that you must merge your changes with /UI2/CL_JSON updates. 9 | 10 | For the second approach, you can use /UI2/CL_JSON directly (the prerequisite is the latest version of note [2330592](https://github.com/SAP/abap-to-json/blob/main/docs/history.md#note-2330592)), you do not need to care about the merge but can override only some methods. The methods are: 11 | 12 | ## IS_COMPRESSIBLE 13 | IS_COMPRESSIBLE is called to check, if the given type output may be suppressed during ABAP to JSON serialization when a value is initial. 14 | 15 | * \> **TYPE_DESCR** (ref to CL_ABAP_TYPEDESCR) – value type 16 | * \< **RV_COMPRESS** (bool) – compress initial value 17 | The default implementation of the method allows compressing any initial value. 18 | 19 | ## PRETTY_NAME 20 | PRETTY_NAME – called to format ABAP field name written to **JSON** or deserialized from **JSON** to **ABAP** field, when the pretty_name parameter of SERIALIZE/DESERIALIZE method equal to PRETTY_MODE-CAMEL_CASE. 21 | 22 | * \> **IN** (CSEQUENCE) – Field name to pretty print. 23 | * \< **OUT** (STRING) – Pretty printed field name 24 | The default implementation applies camelCase formatting using the “\_” symbol. To output, the “\_” symbol, use the double “\_\_” symbol in the field name. 25 | 26 | ## PRETTY_NAME_EX 27 | PRETTY_NAME_EX – called to format ABAP field name written to JSON or deserialized from JSON to ABAP field, when the pretty_name parameter of *SERIALIZE/DESERIALIZE* method equal to *PRETTY_MODE-EXTENDED*. 28 | 29 | * \> **IN** (CSEQUENCE) – Field name to pretty print. 30 | * \< **OUT** (STRING) – Pretty printed field name 31 | The default implementation does the same as PRETTY_NAME, plus converting special characters "!#$%&\*-~/:|@.". 32 | 33 | ## DUMP_INT 34 | DUMP_INT - called for recursive serialization of complex ABAP data objects (structure, class, table) into JSON string 35 | 36 | * \> **DATA** (DATA) – Any data to serialize. 37 | * \> **TYPE_DESCR** (ref to CL_ABAP_TYPEDESCR, optional) – Type of data provided 38 | * \< **R_JSON** (JSON) – serialized JSON value 39 | 40 | ## DUMP_TYPE 41 | DUMP_TYPE - called for serialization of elementary ABAP data type (string, boolean, timestamp, etc) into the JSON attribute value. Overwrite it if you, for example, want to apply data output data conversion of currency rounding 42 | * \> **DATA** (DATA) – Any data to serialize 43 | * \> **TYPE_DESCR** (ref to CL_ABAP_TYPEDESCR) – Type of data provided 44 | * \< **R_JSON** (JSON) – serialized JSON value 45 | 46 | ## RESTORE 47 | RESTORE - called for deserializing JSON objects into ABAP structures 48 | 49 | * \> **JSON** (JSON) – JSON string to deserialize 50 | * \> **LENGTH** (I) – Length of the JSON string 51 | * \> **TYPE_DESCR** (ref to CL_ABAP_TYPEDESCR, optional) – Type of changing data provided 52 | * \> **FIELD_CACHE** (type T_T_FIELD_CACHE, optional) – Cache of ABAP data fields with type information 53 | * \<\> **DATA** (type DATA, optional) – ABAP data object to fill 54 | * \<\> **OFFSET** (I) – parsing start point in JSON string 55 | 56 | ## RESTORE_TYPE 57 | RESTORE_TYPE - called to deserialize simple JSON attributes and JSON arrays 58 | 59 | * \> **JSON** (JSON) – JSON string to deserialize 60 | * \> **LENGTH** (I) – Length of the JSON string 61 | * \> **TYPE_DESCR** (ref to CL_ABAP_TYPEDESCR, optional) – Type of changing data provided 62 | * \> **FIELD_CACHE** (type T_T_FIELD_CACHE, optional) – Cache of ABAP data fields with type information 63 | * \<\> **DATA** (type DATA, optional) – ABAP data object to fill 64 | * \<\> **OFFSET** (I) – parsing start point in JSON string 65 | 66 | ## CLASS_CONSTRUCTOR 67 | CLASS_CONSTRUCTOR - used to initialize static variables. You can not overwrite it but implement your class constructor that adapts default globals. For example, add boolean types to be recognized during serialization/deserialization. 68 | 69 | ## SERIALIZE/DESERIALIZE 70 | SERIALIZE/DESERIALIZE - these methods are static and cannot be redefined. Methods are helpers for a consumption code, hiding the construction of the class instance and further \*\_INT calls. So, if you would like to use something similar, in your custom class, you need to copy the mentioned methods to new ones e,g \*\_EX and overwrite there /UI2/CL_JSON type to your custom class name. And use these methods instead of standard. 71 | 72 | Extension using inheritance: 73 | ```abap 74 | CLASS lc_json_custom DEFINITION FINAL INHERITING FROM /ui2/cl_json. 75 | PUBLIC SECTION. 76 | CLASS-METHODS: 77 | class_constructor, 78 | deserialize_ex IMPORTING json TYPE json OPTIONAL 79 | pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none 80 | CHANGING data TYPE data, 81 | serialize_ex IMPORTING data TYPE data 82 | compress TYPE bool DEFAULT c_bool-false 83 | pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none 84 | RETURNING value(r_json) TYPE json. 85 | 86 | PROTECTED SECTION. 87 | METHODS: 88 | is_compressable REDEFINITION, 89 | pretty_name REDEFINITION, 90 | dump_type REDEFINITION. 91 | ENDCLASS. "lc_json_custom DEFINITION 92 | 93 | CLASS lc_json_custom IMPLEMENTATION. 94 | 95 | METHOD class_constructor. 96 | CONCATENATE mc_bool_types `\TYPE=/UI2/BOOLEAN` INTO mc_bool_types. 97 | ENDMETHOD. "class_constructor 98 | 99 | METHOD is_compressable. 100 | IF type_descr->absolute_name EQ `\TYPE=STRING` OR name EQ `INITIAL`. 101 | rv_compress = abap_false. 102 | ELSE. 103 | rv_compress = abap_true. 104 | ENDIF. 105 | ENDMETHOD. "is_compressable 106 | 107 | METHOD pretty_name. 108 | out = super->pretty_name( in ). 109 | CONCATENATE out 'Xxx' INTO out. 110 | ENDMETHOD. "pretty_name 111 | 112 | METHOD dump_type. 113 | 114 | DATA: is_ddic TYPE abap_bool, 115 | ddic_field TYPE dfies. 116 | 117 | is_ddic = type_descr->is_ddic_type( ). 118 | IF is_ddic EQ abap_true. 119 | ddic_field = type_descr->get_ddic_field( ). 120 | IF mv_ts_as_iso8601 EQ c_bool-true AND ddic_field-domname EQ `TZNTSTMPL`. 121 | r_json = data. 122 | CONCATENATE `"` r_json(4) `-` r_json+4(2) `-` r_json+6(2) `T` r_json+8(2) `:` r_json+10(2) `:` r_json+12(2) `.` r_json+15(7) `Z"` INTO r_json. 123 | RETURN. 124 | ENDIF. 125 | ENDIF. 126 | IF mv_ts_as_iso8601 EQ c_bool-true AND type_descr->absolute_name EQ `\TYPE=LCM_CHANGED_ON`. 127 | r_json = data. 128 | CONCATENATE `"` r_json(4) `-` r_json+4(2) `-` r_json+6(2) `T` r_json+8(2) `:` r_json+10(2) `:` r_json+12(2) `.` r_json+15(7) `Z"` INTO r_json. 129 | RETURN. 130 | ENDIF. 131 | 132 | r_json = super->dump_type( data = data type_descr = type_descr convexit = convexit ). 133 | 134 | ENDMETHOD. "dump_type 135 | 136 | METHOD serialize_ex. 137 | DATA: lo_json TYPE REF TO lc_json_custom. 138 | CREATE OBJECT lo_json 139 | EXPORTING 140 | compress = compress 141 | pretty_name = pretty_name 142 | assoc_arrays = abap_true 143 | assoc_arrays_opt = abap_true 144 | expand_includes = abap_true 145 | numc_as_string = abap_true 146 | ts_as_iso8601 = abap_true. 147 | r_json = lo_json->serialize_int( data = data ). 148 | ENDMETHOD. "serialize_ex 149 | 150 | METHOD deserialize_ex. 151 | DATA: lo_json TYPE REF TO lc_json_custom. 152 | IF json IS NOT INITIAL. 153 | CREATE OBJECT lo_json 154 | EXPORTING 155 | pretty_name = pretty_name 156 | assoc_arrays = abap_true 157 | assoc_arrays_opt = abap_true. 158 | TRY . 159 | lo_json->deserialize_int( EXPORTING json = json CHANGING data = data ). 160 | CATCH cx_sy_move_cast_error. 161 | ENDTRY. 162 | ENDIF. 163 | ENDMETHOD. "deserialize_ex 164 | 165 | ENDCLASS. "lc_json_custom IMPLEMENTATION 166 | 167 | TYPES: 168 | BEGIN OF tp_s_data, 169 | tribool TYPE lc_json_custom=>tribool, 170 | bool TYPE lc_json_custom=>bool, 171 | str1 TYPE string, 172 | str2 TYPE string, 173 | initial TYPE i, 174 | END OF tp_s_data. 175 | 176 | DATA: ls_exp TYPE tp_s_data, 177 | ls_act LIKE ls_exp, 178 | lo_json_custom TYPE REF TO lc_json_custom, 179 | lv_json_custom TYPE lc_json_custom=>json. 180 | 181 | ls_exp-tribool = lc_json_custom=>c_tribool-false. 182 | ls_exp-bool = lc_json_custom=>c_bool-false. 183 | ls_exp-str1 = ''. 184 | ls_exp-str2 = 'ABC'. 185 | ls_exp-initial = 0. 186 | CREATE OBJECT lo_json_custom 187 | EXPORTING 188 | compress = abap_true 189 | pretty_name = lc_json_custom=>pretty_mode-camel_case. 190 | 191 | lv_json_custom = lo_json_custom->serialize_int( data = ls_exp ). 192 | lo_json_custom->deserialize_int( EXPORTING json = lv_json_custom CHANGING data = ls_act ). 193 | 194 | " alternative way 195 | lc_json_custom=>deserialize_ex( EXPORTING json = lv_json_custom CHANGING data = ls_act ). 196 | cl_aunit_assert=>assert_equals( act = ls_act exp = ls_exp msg = 'Custom pretty name fails!' ). 197 | 198 | WRITE / lv_json_custom. 199 | ``` 200 | Results in the following JSON: 201 | ```json 202 | { 203 | "triboolXxx": false, 204 | "str1Xxx": "", 205 | "str2Xxx": "ABC", 206 | "initialXxx": 0 207 | } 208 | ``` 209 | 210 | # Continue reading 211 | * [Basic usage of the class](basic.md) 212 | * [Advanced Use cases](advanced.md) 213 | * [FAQ](faq.md) 214 | * [Version History](history.md) 215 | -------------------------------------------------------------------------------- /docs/data-access.md: -------------------------------------------------------------------------------- 1 | # Dynamic Data Accessor Helper Class for ABAP 2 | 3 | # Why 4 | Sometimes you need to access [ABAP data objects dynamically](https://help.sap.com/http.svc/rc/abapdocu_751_index_htm/7.51/en-US/abendyn_access_data_obj_guidl.htm). For example, when: 5 | * you do not know the structure of the ABAP object and to dynamically access the value of the field, you are forced to use ASSIGN .. COMPONENT with field symbol 6 | * you do a cross-release development in ABAP and do not know if some field exists, so you need dynamic access to data 7 | * you access data from the optional ABAP component and also need dynamic access to the ABAP structure field or internal table row 8 | * you need a key or index access to a dynamic internal table 9 | 10 | Normally, if you need to access ABAP fields/structure components dynamically, you will end up coding like this: 11 | 12 | ## Accessing Data Objects Dynamically (as suggested in the documentation) 13 | ```abap 14 | FIELD-SYMBOLS: TYPE ANY TABLE, 15 | TYPE ANY, 16 | TYPE ANY. 17 | 18 | ASSIGN COMPONENT `FIELD1` OF STRUCTURE TO . 19 | IF IS ASSIGNED. 20 | WRITE: . 21 | ENDIF. 22 | ``` 23 | That may be OK (however still not very convenient), but if you have a dynamic ABAP object with a deeper structure, it becomes boring: 24 | 25 | ## Accessing Deeply Nested Data Objects Dynamically 26 | ```abap 27 | FIELD-SYMBOLS: 28 |
TYPE ANY TABLE, 29 | TYPE ANY, 30 | TYPE ANY, 31 | TYPE ANY, 32 | TYPE ANY, 33 | TYPE ANY. 34 | 35 | ASSIGN COMPONENT `FIELD1` OF STRUCTURE TO . 36 | IF IS ASSIGNED. 37 | ASSIGN COMPONENT `CHILD_1` OF STRUCTURE TO . 38 | IF IS ASSIGNED. 39 | ASSIGN COMPONENT `CHILD_11` OF STRUCTURE TO . 40 | IF IS ASSIGNED. 41 | ASSIGN COMPONENT `CHILD_111` OF STRUCTURE TO . 42 | IF IS ASSIGNED. 43 | WRITE: . 44 | ENDIF. 45 | ENDIF. 46 | ENDIF. 47 | ENDIF. 48 | ``` 49 | 50 | And if one thinks about accessing dynamically elements from nested tables it would be at all - hell. 51 | So, below one can find a helper class, which may help in such cases, in a lean and tasty way. 52 | An original and actual version of the source can be found in class /UI2/CL_DATA_ACCESS delivered with UI2 Add-on (can be applied to SAP_BASIS 700 – 76X). So, you can use this ABAP JSON parser in your standard code mostly on any system. Delivered with a note [2526405](https://help.sap.com/http.svc/rc/abapdocu_751_index_htm/7.51/en-US/abendyn_access_data_obj_guidl.htm). 53 | 54 | # What it can 55 | The accessor is a single class, with the following features: 56 | * traversing of any data object (classes are not yet supported) passed as a reference to data or as data. 57 | * traversing nested objects without any level limitations 58 | * automatically resolving reference variables (REF TO fields accessed the same way as standard fields) 59 | * a method like or XPath-like ways of accessing data 60 | * read/modification access to elementary types 61 | * accessing table rows using index or key 62 | 63 | ## Limitations 64 | The code of the dynamic data accessor class does not pretend to be a complete and fully robust solution, but it may become like this if requests come 65 | Current limitations are the following: 66 | * only ABAP data structures are supported. Traversing of ABAP objects/classes not yet supported, however possible 67 | * dynamic modification of tables (not data inside) is not supported. E.g you can not add/remove rows with API, but you can do it via reference to the table 68 | * The syntax for key access of the rows in dynamic tables does not allow usage of the symbol "," as part of the query. No escaping is supported. 69 | 70 | # Usage 71 | ## Necessary type and data declarations for example below 72 | ```abap 73 | TYPES: 74 | BEGIN OF t_properties, 75 | enabled TYPE abap_bool, 76 | length TYPE i, 77 | description TYPE c LENGTH 20, 78 | END OF t_properties, 79 | BEGIN OF t_data, 80 | id TYPE i, 81 | properties TYPE t_properties, 82 | content TYPE string, 83 | END OF t_data, 84 | BEGIN OF t_name_value, 85 | name TYPE string, 86 | value TYPE string, 87 | END OF t_name_value, 88 | BEGIN OF t_example, 89 | flag TYPE abap_bool, 90 | props TYPE t_data, 91 | params TYPE SORTED TABLE OF t_name_value WITH UNIQUE KEY name, 92 | END OF t_example. 93 | 94 | DATA: ls_data TYPE t_example, 95 | lr_data TYPE REF TO data, 96 | lr_ref TYPE REF TO data, 97 | lv_int TYPE i, 98 | lo_data TYPE REF TO /ui2/cl_data_access. 99 | 100 | FIELD-SYMBOLS: TYPE data. 101 | 102 | ls_data-props-id = 12345. 103 | ls_data-props-content = `Some Content`. 104 | ls_data-props-properties-enabled = abap_false. 105 | ls_data-props-properties-length = 10. 106 | ls_data-props-properties-description = `My description`. 107 | 108 | GET REFERENCE OF ls_data INTO lr_data. 109 | ``` 110 | 111 | ## Usage examples 112 | 113 | ### Traversing ABAP data dynamically 114 | Simplest usage example: 115 | ```abap 116 | CREATE OBJECT lo_data EXPORTING ir_data = lr_data. 117 | 118 | " standard way (does not work on SAP_BASIS 700) 119 | lr_ref = lo_data->at(`PROPS`)->at(`id`)->ref( ). 120 | IF lr_ref IS BOUND. 121 | ASSIGN lr_ref->* TO . 122 | WRITE: . 123 | ENDIF. 124 | " XPath like 125 | lr_ref = lo_data->at(`PROPS-ID`)->ref( ). 126 | IF lr_ref IS BOUND. 127 | ASSIGN lr_ref->* TO . 128 | WRITE: . 129 | ENDIF. 130 | 131 | " using helper method for creation 132 | lr_ref = /ui2/cl_data_access=>create( ir_data = lr_data iv_component = `PROPS-ID`)->ref( ). 133 | IF lr_ref IS BOUND. 134 | ASSIGN lr_ref->* TO . 135 | WRITE: . 136 | ENDIF. 137 | 138 | " reading value directly 139 | lo_data->at(`props-properties-length`)->value( IMPORTING ev_data = lv_int ). 140 | WRITE: lv_int. 141 | ``` 142 | 143 | ### Dynamic data modification in ABAP in a nice way 144 | If you want to use modification operations, you can only operate with references when creating an accessor object. 145 | ```abap 146 | " modifing value 147 | lr_ref = lo_data->at(`props-properties-length`)->ref( ). 148 | ASSIGN lr_ref->* TO . 149 | = 25. 150 | 151 | " or even more simple 152 | lo_data->at(`props-properties-length`)->set( 15 ). 153 | ``` 154 | 155 | ### Accessing not existing component 156 | The code is robust - accessing existing components of any level will not result in a crash but will return an empty reference or initial value. 157 | ```abap 158 | " reading not existing value returns the initial value 159 | lo_data->at(`props-properties-not_exist-length-not-exist`)->value( IMPORTING ev_data = lv_int ). 160 | WRITE: lv_int. " -> 0 161 | ``` 162 | 163 | ### Working with tables: 164 | Dynamic Access in ABAP in an easy way 165 | ```abap 166 | DATA: 167 | ls_data TYPE t_example, 168 | ls_line LIKE LINE OF ls_data-params, 169 | lv_value TYPE string, 170 | lo_data TYPE REF TO /ui2/cl_data_access. 171 | 172 | ls_line-name = `KEY1`. 173 | ls_line-value = `Value1`. 174 | INSERT ls_line INTO TABLE ls_data-params. 175 | 176 | ls_line-name = `KEY2`. 177 | ls_line-value = `Value2`. 178 | INSERT ls_line INTO TABLE ls_data-params. 179 | 180 | /ui2/cl_data_access=>create( iv_data = ls_data iv_component = `params[2]-name`)->value( IMPORTING ev_data = lv_value ). 181 | WRITE: lv_value. 182 | 183 | /ui2/cl_data_access=>create( iv_data = ls_data iv_component = `params[name=KEY1]-value`)->value( IMPORTING ev_data = lv_value ). 184 | WRITE: lv_value. 185 | 186 | /ui2/cl_data_access=>create( iv_data = ls_data iv_component = `params`)->at(`[name=KEY1]-value`)->value( IMPORTING ev_data = lv_value ). 187 | WRITE: lv_value. 188 | 189 | /ui2/cl_data_access=>create( iv_data = ls_data iv_component = `params[name=KEY1, value=Value1]-value`)->value( IMPORTING ev_data = lv_value ). 190 | WRITE: lv_value. 191 | ``` 192 | 193 | # API description 194 | 195 | ## CREATE - Static Method Public Helper method for creating an instance of dynamic accessor 196 | * \> IR_DATA (ref to data) - Importing Type Ref To DATA Reference to data (allows modification of embedded data) 197 | * \> IV_DATA (data) - any data (modification of embedded data not allowed) 198 | * \> IV_COMPONENT (string) - Sub-component name (XPath-like syntax is supported) 199 | * \< RO_REF (ref to /ui2/cl_data_access) - Reference to accessor object pointing to subcomponent 200 | 201 | ## CONSTRUCTOR - Instance Method Public Constructor 202 | * \> IR_DATA (ref to data) - Importing Type Ref To DATA Reference to data (allows modification of embedded data) 203 | * \> IV_DATA (data) - any data (modification of embedded data not allowed) 204 | 205 | ## AT - Instance Method Public Component accessor 206 | * \> IV_COMPONENT (string) - Sub-component name (XPath-like syntax is supported) 207 | * \< RO_REF (ref to /ui2/cl_data_access) - Reference to accessor object pointing to subcomponent 208 | 209 | ## EMPTY - Instance Method Public Returns TRUE if the embedded object is initial (not bound) 210 | * \< RV_VAL (boolean) - ABAP_TRUE if the object is initial and data is not bound 211 | 212 | ## REF - Instance Method Public Returns a reference to the embedded object 213 | * \< RV_DATA (ref to data) - Reference to embedded data 214 | 215 | ## VALUE - Instance Method Public Returns copy of the value 216 | * \< EV_DATA (data) - Copy of the embedded data value, or initial if data is not bound 217 | 218 | ## SET - Instance Method Public Sets the value of the embedded object, if not initial 219 | * \> IV_DATA (data) - New value for embedded object 220 | * \< RV_SUCCESS (boolean) - ABAP_TRUE, if data was successfully modified 221 | 222 | # XPath-like dynamic data access 223 | To access nested components you can use a nice, object-oriented way, using nested calls of AT method (it is robust, and would not crash accessing not existing components), as 224 | ```abap 225 | lo_object->at('subcomp1')->at('subcomp11')->... 226 | ``` 227 | 228 | But if you prefer a more compact form, or run code on SAP_BASIS < 702 (nested method calls are not supported), you may use XPath-like syntax for accessing components. Like this: 229 | ```abap 230 | lo_object->at('subcomp1-subcomp11-subcomp111') 231 | ``` 232 | 233 | or like this 234 | ``` 235 | abap lo_object->at('subcomp1->subcomp11->subcomp111') 236 | ``` 237 | 238 | ## The syntax: 239 | * You can use any symbol (or combinations of symbols) as a component separator, except "[", "]", "=", ",". The recommended separator symbol is "-". 240 | * For dynamic index access of rows in nested tables use "[index]" after the component name. E.g. "table_name[2]". The indexing starts from 1. If the accessor object references the table data object directly, you may skip the component name. E.g. "[2]". You may continue accessing components after index access, e.g.: "table_a[1]-struct-table_b[2]". If you do out-of-range access, you get an empty reference back. Index access works only with index tables (STANDARD, SORTED). 241 | * For dynamic key access of rows in nested tables use "(key=value)" for single key lookup, "(key1=value1, key2=value2)" for multi-key lookup, and "(value)" for table line lookup. You can NOT search for values containing ",". You can use nested lookups: "table_a(key1=value1)-table_b(key2=value2, key3=value3)". Values used in a query shall be assignable to field structures. 242 | 243 | # Version History 244 | 245 | ## Note [2798102](https://launchpad.support.sap.com/#/notes/2798102) - PL12 246 | * Fixed. Access to fields with special characters in the name (e.g. "/BIC/YEAR") fails. 247 | 248 | ## Note [2786259](https://launchpad.support.sap.com/#/notes/2786259) - PL11 249 | * Fixed. Short dump, when accessing elements of a null array 250 | 251 | ## Note [2526405](https://launchpad.support.sap.com/#/notes/2526405) 252 | * New: /UI2/CL_DATA_ACCESS class for working with dynamic ABAP data object (generated with method /UI2/CL_JSON=>GENERATE). The class can be used as a replacement for multiple ASSIGN COMPONENT language constructions. 253 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | * [It is slow](#it-is-slow) 4 | * [GENERATE or DESERIALIZE into REF TO DATA vs. DESERIALIZE into a typed data structure](#generate-or-deserialize-into-ref-to-data-vs-deserialize-into-a-typed-data-structure) 5 | * [JSON to ABAP data type conversion when using GENERATE or DESERIALIZE into REF TO DATA](#json-to-abap-data-type-conversion-when-using-generate-or-deserialize-into-ref-to-data) 6 | * [Serialize huge data objects into JSON and short dumps](#serialize-huge-data-objects-into-json-and-short-dumps) 7 | * [Encoding of Unicode characters (for example Chinese)](#encoding-of-unicode-characters-for-example-chinese) 8 | * [Incompatible change for initial date/time fields serializing with PL16](#incompatible-change-for-initial-datetime-fields-serializing-with-pl16) 9 | * [Is there a way to deserialize objects that have references to Interface?](#is-there-a-way-to-deserialize-objects-that-have-references-to-interface) 10 | * [Is it possible to have a defined order of fields in ABAP structures generated when deserializing into REF TO DATA fields? Is it possible to have the fields in the generated structure in the same order as in the JSON file?](#is-it-possible-to-have-a-defined-order-of-fields-in-abap-structures-generated-when-deserializing-into-ref-to-data-fields-is-it-possible-to-have-the-fields-in-the-generated-structure-in-the-same-order-as-in-the-json-file) 11 | * [Is it possible to display the currency amount (CURR fields) formatted in the JSON output based on the related currency (CUKY field)?](#is-it-possible-to-display-the-currency-amount-curr-fields-formatted-in-the-json-output-based-on-the-related-currency-cuky-field) 12 | * [My fields are/NOT serialized as true/false instead and serialized like 'X' or ''! E.g. how to control ABAP/JSON Boolean conversion?](#my-fields-arenot-serialized-as-truefalse-instead-and-serialized-like-x-or--eg-how-to-control-abapjson-boolean-conversion) 13 | * [I can not use /UI2/CL_JSON for ABAP Cloud BADi development](#i-can-not-use-ui2cl_json-for-abap-cloud-badi-development) 14 | * [You get a short dump OBJECTS_NOT_CHAR when serializing data with enabled conversion exits](#you-get-a-short-dump-objects_not_char-when-serializing-data-with-enabled-conversion-exits) 15 | * [Why special characters in JSON attribute names are not escaped or unescaped?](#why-are-special-characters-in-json-attribute-names-not-escaped-or-unescaped) 16 | 17 | ## It is slow 18 | It is as fast as possible to achieve it in pure ABAP and already heavily optimized. If you have suggestions on how to make it faster - you are welcome. Features like type conversions, type detections, renaming, data generation, etc require processing time, and even if they are not active you may pay the penalty because the class design allows this feature. Operations on strings are not fast in ABAP, and method calls are costly, which is why macros are used within the class. However, the class is robust and can handle any data type for serialization and deserialization, offering many convenient functions that would otherwise need to be implemented manually. It performs well in numerous use cases. 19 | 20 | In scenarios where extensive functionality and flexibility are unnecessary, such as with simple, flat tables requiring fast JSON serialization/deserialization, tweaking /ui2/cl_json for speed by disabling flags is not viable. No other universal classes with comparable functionality are known to be faster. The best solutions currently are: 21 | 22 | * **CALL TRANSFORMATION id**: This is the fastest alternative (10x), but it requires accepting a proprietary JSON format with missing type conversions and UPPER case attribute names. It can be suitable if you control the other side and can parse the proprietary JSON. 23 | * **CALL TRANSFORMATION with custom XSLT**: This option is slower (~7x, depending on XSLT complexity) but allows control over the JSON format through custom XSLT, including attribute names and values. However, it works only with ABAP component names and values, without RTTI information. You need to write, deliver, and synchronize your XSLT with your data structures. 24 | 25 | For more details, please refer to the documentation: [JSON to ABAP transformation with the use of CALL TRANSFORMATION](https://github.com/SAP/abap-to-json/blob/main/docs/advanced.md#json-to-abap-transformation-with-the-use-of-call-transformation) 26 | 27 | ## GENERATE or DESERIALIZE into REF TO DATA vs. DESERIALIZE into a typed data structure 28 | It is always better to deserialize into explicit data structure but not into anonymous reference: 29 | 30 | 1. It is faster 31 | 2. It is type-safe 32 | 3. Processing deserialized results is much easier. 33 | Deserializing into REF TO data is the same as using the GENERATE method and results in generating real-time ABAP types, which is quite slow. You can not specify the resulting types for data elements and the deserializer needs to guess types. To process generated results, you always use dynamic programming, which is by default slow (or [/UI2/CL_DATA_ACCESS](https://github.com/SAP/abap-to-json/blob/main/docs/data-access.md), which is more comfortable but still uses dynamic programming inside). 34 | 35 | ## JSON to ABAP data type conversion when using GENERATE or DESERIALIZE into REF TO DATA 36 | The data type selection logic of the GENERATE method (DESERIALIZE into REF TO data) is not guaranteed or defined. The class makes the best guess for the resulting ABAP data type based on the JSON value and the best-fitting data type on the ABAP side. For example, JSON booleans convert to ABAP_BOOL, JSON numbers can convert to I, P, or F types depending on the value, and JSON strings convert to date, time, or timestampl if the value matches a pattern, otherwise, they convert to a string. A new type of conversion may be introduced in the future. If you do not provide a fixed ABAP structure, you must be prepared to work with any of the generated types. Additionally, if you use the GEN_OPTIMIZE flag, you may receive direct types instead of references. For explicit data types, deserialize into a fixed structure. 37 | 38 | ## Serialize huge data objects into JSON and short dumps 39 | You are using the class to serialize your data into JSON. Unfortunately, sometimes you pass too big tables, which results in too long a JSON string (for example, longer than 1GB), and this leads to dumps like SYSTEM_NO_ROLL, STRING_SIZE_TOO_LARGE, MEMORY_NO_MORE_PAGING, while the system can not allocate such a big continuous memory chunk. This specific case could be solved by increasing the memory allocation limit, but you would still end up with an INT4 size limit for string length, which can not be more than 2GB. 40 | 41 | The string (JSON) of such size can not be created, transported, or persisted. You would need to have special handling on your side for this case. 42 | E.g. if you want to serialize such a big amount of data, you will need to split the input into chunks and do the serialization and transport of the resulting JSON chunks by parts. 43 | 44 | The memory exceptions are not catchable and you will need to do data size evaluations on your side, before calling serialization. 45 | 46 | So, the only robust way to solve the issue will be by limiting serialized data size, which can be done only on the /ui2/cl_json consumer side. 47 | 48 | Even if you select another format for serialization (XML or ABAP JSON) you will stick to some limit. So, no other way. 49 | 50 | If you still need to serialize everything, you may split data into chunks and give them to the serializer one by one. Then deserialize all fragments into the same data object for merging. 51 | 52 | ## Encoding of Unicode characters (for example Chinese) 53 | The serializer does not do any explicit character encoding, this is done by ABAP. Normally, ABAP works with UTF16, a 2-byte Unicode encoding that can represent any character (also Chinese). That is why you see Chinese characters in the debugger. Later on, after serializing in JSON (you may also check in debugger JSON and see that Chinese characters are still in), you pass the JSON string further, maybe as a REST response. And there is, probably, converted into UTF8 encoding, which is multibyte encoding, where some characters (Latin) are encoded with one byte and some (Chinese, Russian, etc.) as multibyte. Then the viewer of such UTF8 text shall be able to interpret and display them properly. If you do not see characters as expected in your viewer tool, then, probably, nothing is corrupt and the receiver will get them fine. It is just an issue of the viewer that does not recognize UTF8, or probably, lost an encoding ID interpreted wrong. 54 | 55 | ## Incompatible change for initial date/time fields serializing with PL16 56 | First of all, I would agree that this is an incompatible change and I am asking you to excuse me for your efforts. It was an intentional change and I was aware that someone can already rely on current behavior and may get issues. 57 | 58 | The reason for this change of default was a customer complaint regarding the handling of initial date-time values, which are not 0000-00-00 or 00:00:00. In general 0000-00-00 is an invalid date, 00:00:00 is valid, but how to understand that it is initial but not explicit midnight? 59 | 60 | Because of that, I have decided not to render initial values for date/time and give a receiver a way to understand that it is initial and has its default/initial processing. I know that it is incompatible, but I want a default behavior to be the best and most common choice, even with the cost of modification of the consumer code that relies on old behavior :/. 61 | 62 | Because having custom rendering of the initial date/time is quite exotic, I have only let this for constructor calls and have not extended the serialize method, to keep standard API simple. If I get multiple requests regarding extending SERIALIZE with these defaults - I will do it. 63 | 64 | My recommendation for you: 65 | * Variant 1: Adopt your unit tests for new initial values for date-time. 66 | * Variant 2: Use the instance method for serialization. E.g. parametrized CONSTRUCTOR + SERIALIZE_INT (in this case you can customize behavior by parametrizing instance constructor with desired initial values for time date). 67 | * Variant 2: Extend the /ui2/cl_json class or create a helper method in your class with your static customized SERIALIZE call which already considers new defaults for the /ui2/cl_json constructor. 68 | 69 | ## Is there a way to deserialize objects that have references to Interface? 70 | **Q**: I am using /ui2/cl_json to serialize an object that contains some reference attributes. These reference attributes are TYPE REF TO . Upon deserialization, the references are not getting deserialized. Is there a way to deserialize objects that have references to Interface? 71 | 72 | **A**: Unfortunately - not. To deserialize an object, it shall be created. And how would you like to create an instance of the interface without knowing the class? It can not be done automatically. But you may try to [implement the deserialization logic by yourself](docs/advanced.md#jsonabap-serializationdeserialization-with-runtime-type-information). 73 | 74 | ## Is it possible to have a defined order of fields in ABAP structures generated when deserializing into REF TO DATA fields? Is it possible to have the fields in the generated structure in the same order as in the JSON file? 75 | The order of fields in JSON and also in ABAP is undefined. It may happen that you will have two records of the same type in an array but with attributes serialized in different orders. 76 | What to do in this case? In general, the answer is – no (there is no way to configure it). The current alphabetical order gives at least some predefined output (but the result is a name normalization and uniqueness check). 77 | 78 | If you want a specific order, just deserialize it in a predefined structure, but not in REF TO DATA. Generating into REF TO DATA is always a bad choice (from a performance and type definition perspective). 79 | 80 | But there is not an easy way, in the latest releases of the class. 81 | If you still want a predefined sequence of the fields in the generated structure, you may inherit the class, and prefill structure buffer (mt_struct_type) in your inherited class constructor. See method GENERATE_STRUCT for details. In this case, later deserialize/generate calls will use your structure type, but not one created with default logic. 82 | 83 | ## Is it possible to display the currency amount (CURR fields) formatted in the JSON output based on the related currency (CUKY field)? 84 | No, there is no built-in support for currency fields. Potentially one can add it in a derived class, overwriting dump_int and restore_type methods, but I do not want to have it in by default, because of implementation complexity and performance penalty. 85 | 86 | Only single-field conversion exits are supported. 87 | 88 | ## My fields are/NOT serialized as true/false instead and serialized like 'X' or ''! E.g. how to control ABAP/JSON Boolean conversion? 89 | JSON, as JavaSctript, has a built-in Boolean type with true/false values. ABAP does not have a built-in Boolean type and uses fields of char 1 with constant values of 'X' (TRUE) and ''(space, FALSE). Different teams use different predefined types to be used as a Boolean type for them. It is a zoo. There is no way to detect the boolean type or even be able to auto-convert them between different ABAP types. However, there are some more or fewer standard conventions for which standard types shall be used for booleans. The serializer class has the default list of standard boolean types hardcoded in constant MC_BOOL_TYPES (ABAP_BOOLEAN, ABAP_BOOL, BOOLEAN, BOOLE_D, XFELD, XSDBOOLEAN, WDY_BOOLEAN). If you use one of these types in your data, passed to the serializer, it will be automatically processed by the parser and converted from ''/'X' into false/true and vice versa. If you use any other type not in the list, there will be no auto-conversion. OK, you do not like default (it processes too many types or two less), what do you do? You have the following choices: 90 | * Do not use static methods for serialization and deserialization, but instance ones (e.g. json_obj->serilaize_int instead of /ui2/cl_json=>serilaize) AND customize the behavior of the instance by constructor parameters. You can pass an alternative set of boolean types with the parameter BOOL_TYPES. The usage of instance methods is also faster if you repeat calls for serialization. 91 | * Inherit the class and overwrite default boolean types, stored in the class variable mv_bool_types with your preferred default. Use your class everywhere instead of standard, to ensure consistency. How to inherit the class you can find [here](docs/class-extension.md). 92 | 93 | ## I can not use /UI2/CL_JSON for ABAP Cloud BADi development 94 | The class has been released for ABAP Cloud development (Steampunk) for a long time. Initially, it was released only for the Public Cloud, and the Private Cloud was missed by mistake. That was corrected and now you can use it for Private Cloud from release OP 2023 (SAP_BASIS 758). See details [here](docs/history.md#note-3424850-ui2cl_json-release-api-for-cloud-development). If you need JSON processing in ABAP Cloud BADis, you may need to use the XCO library (XCO_JSON), which is meant to be the official JSON processing library for Key User Extensibility Apps. If you still think that the use of /UI2/CL_JSON would be preferable you may ask for releasing of it via the Customer Influence program, like it was [done for Steampunk sometime](https://influence.sap.com/sap/ino/#/idea/234724/?section=sectionVotes). 95 | 96 | ## You get a short dump OBJECTS_NOT_CHAR when serializing data with enabled conversion exits 97 | You can apply conversion exits to serialized data when using the /ui2/cl_json. The class uses a temporary buffer of type STRING as an output for conversion exits. But some old conversion exits support only writing in char-like variables (C LENGHT ...) (restriction of WRITE TO) and dumping when WRITE TO is executed with STRING output. But the current [programming guidelines for conversion exits](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abenconversion_exits.htm) say that output can be [C-LIKE type](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abenbuilt_in_types_generic.htm) (c, n, and string, as well as the date/time types d, t and character-like flat structures). So it can be a string. 98 | If you get such a dump, please raise a message and ask the conversion exit owner to update the code to support STRING type, following the example implementation for CONVERSION_EXIT_SDURA_OUTPUT from SAP Help]: 99 | ```abap 100 | FUNCTION CONVERSION_EXIT_SDURA_OUTPUT. 101 | *"---------------------------------------------------------------------- 102 | *"*"Local Interface: 103 | *" IMPORTING 104 | *" VALUE(INPUT) 105 | *" EXPORTING 106 | *" VALUE(OUTPUT) TYPE CLIKE 107 | *"---------------------------------------------------------------------- 108 | 109 | hours = input DIV 60. 110 | minutes_n = input MOD 60. 111 | 112 | DESCRIBE FIELD output TYPE DATA(typ). 113 | 114 | IF typ = 'g'. "OUTPUT is type string, no WRITE TO for strings, enabled with string templates 20130423, KELLERH 115 | output = |{ hours WIDTH = 3 ALIGN = RIGHT }:{ minutes_n WIDTH = 2 }|. 116 | ELSE. "Old overflow behavior to stay compatible 117 | WRITE hours TO output(3) NO-SIGN. 118 | output+3(1) = ':'. 119 | WRITE minutes_n TO output+4(2). 120 | ENDIF. 121 | ENDFUNCTION. 122 | ``` 123 | Use of output buffer TYPE C LENGHT ... in code of /ui2/cl_json would require an additional CONDENSE call that would negatively impact the performance of serialization and may still lead to incorrect data rendering (the logic with TYPE C LENGHT... was in PL19, but is reverted with PL 20, because on [this issue](issues/10)). 124 | 125 | ## Why are special characters in JSON attribute names not escaped or unescaped? 126 | This is a known limitation. Escaping, and especially unescaping, is very performance-critical and will significantly influence parsing time. Cases where attribute names contain special characters are quite unique — ABAP field names do not allow special characters. So, to optimize overall performance, I have decided not to support this. The only cases when the parser does escaping and unescaping the attribute names are usages of the ASSOC_ARRAYS flag when table key values are converted into leading attribute names (associative arrays in terms of JSON) and generation of the structures (internally it also uses ASSOC_ARRAY flag). 127 | 128 | # Continue reading 129 | * [Basic usage of the class](basic.md) 130 | * [Advanced Use cases](advanced.md) 131 | * [Version History](history.md) 132 | -------------------------------------------------------------------------------- /docs/history.md: -------------------------------------------------------------------------------- 1 | # Version History 2 | * [Note 3568088 - PL22](#note-3615316---pl22) 3 | * [Note 3568088 - PL21](#note-3568088---pl21) 4 | * [Note 3515438 - PL20](#note-3515438---pl20) 5 | * [Note 3414589 - PL19](#note-3414589---pl19) 6 | * [Note 3424850 - /UI2/CL_JSON - Release API for Cloud Development](#note-3424850---ui2cl_json---release-api-for-cloud-development) 7 | * [Note 3315430 - PL18](#note-3315430---pl18) 8 | * [Note 3106267 - PL17](#note-3106267---pl17) 9 | * [Note 3038042 - PL16](#note-3038042---pl16) 10 | * [Note 2944398 - PL15](#note-2944398---pl15) 11 | * [Note 2904870 - PL14](#note-2904870---pl14) 12 | * [Note 2870163 - PL13](#note-2870163---pl13) 13 | * [Note 2798102 - PL12](#note-2798102---pl12) 14 | * [Note 2786259 - PL11](#note-2786259---pl11) 15 | * [Note 2763854 - PL10](#note-2763854---pl10) 16 | * [Note 2650040](#note-2650040) 17 | * [Note 2629179](#note-2629179) 18 | * [Note 2526405](#note-2526405) 19 | * [Note 2292558](#note-2292558) 20 | * [Note 2300508](#note-2300508) 21 | * [Note 2330592](#note-2330592) 22 | * [Note 2368774](#note-2368774) 23 | * [Note 2382783](#note-2382783) 24 | * [Note 2429758](#note-2429758) 25 | * [Note 2480119](#note-2480119) 26 | 27 | ## Note [3615316](https://launchpad.support.sap.com/#/notes/3615316) - PL22 (not released) 28 | ### /UI2/CL_JSON 29 | * Fixed: Bug with generation of the structures with similar field names (fex, "a", "bc" vs "ab", "c"). 30 | 31 | ## Note [3568088](https://launchpad.support.sap.com/#/notes/3568088) - PL21 32 | ### /UI2/CL_JSON 33 | * Fixed: The wrong elementary type is used when generating data for REF TO data (TIMESTAMP instead of string) 34 | * Fixed: attribute names created from table key values are not escaped/unescaped when using the ASSOC_ARRAYS option. 35 | * Fixed: additional performance optimization for deserializing long strings with escaped characters. Because of the use of CALL TRANSFORMATION id for JSON, it requires kernel updates for SAP_BASIS 7.02 and 7.31 (SAP Notes 1648418 and 1650141). 36 | 37 | ## Note [3515438](https://launchpad.support.sap.com/#/notes/3515438) - PL20 38 | ### /UI2/CL_JSON 39 | * Fixed: Empty objects ( "a":{} ) are not initialized when using GENERATE or DESERIALIZE into REF TO data, taking the value of the previous field 40 | * Added: Support for PascalCase for pretty printing was added. 41 | * Fixed. The logic for OUTPUT conversion exits reverted to the state of PL18 ([details](https://github.com/SAP/abap-to-json/issues/10)). 42 | 43 | ## Note [3414589](https://launchpad.support.sap.com/#/notes/3414589) - PL19 44 | ### /UI2/CL_JSON 45 | * Fixed: enhanced processing of deserialization into typed TYPE REFs 46 | * Fixed: deserialization of JSON 'null' into complex, not reference ABAP fields does not lead to exception in strict mode ([details](https://github.com/SAP/abap-to-json/pull/5)) 47 | * Fixed: Support for ABAP_BOOLEAN type was added. 48 | * Fixed: performance optimization for deserializing strings with escaped line breaks and special characters. When you have escaped "\\", e.g., "\\\\", it is still slow. 49 | * Fixed: short dump in conversion exits routines on SERIALIZE/DESERIALIZE because of the wrong data type (OBJECTS_NOT_CHAR). 50 | * Fixed. Added support for the timezone offsets for ISO8601. 51 | * Fixed: rounding bug when deserializing timestamps with sub-seconds into short timestamps (seconds) 52 | * New: Information about the invalid field added to the exception data of move_cast_error ([details](https://github.com/SAP/abap-to-json/pull/8)) 53 | * New: Serialization now can detect "timestamps" defined with the help of data domains ([details](https://github.com/SAP/abap-to-json/pull/9)) 54 | * New: new switch for GENERATE (optimize) and DESERIALIZE (gen_optimize) that enables optimization of generated ABAP data for REF TO DATA (fewer references, easily readable and accessible). Results in longer processing. 55 | * New: added detection of time, date, and timestamp values on generation/deserialization into TYPE REF TO data. 56 | 57 | ## Removed dependency to /ui2/cl_data_access for z_ui2_json 58 | * Added: Open Source version of the /ui2/cl_data_access as Z_UI2_DATA_ACCESS 59 | 60 | ## Note [3424850](https://launchpad.support.sap.com/#/notes/3424850) - /UI2/CL_JSON - Release API for Cloud Development 61 | You can not use the class in the Private Cloud, because the class has not been released for Cloud Development (for the Public Cloud, it was already released). 62 | Delivered with OP 2025 and OP 2023 FPS2. Note for OP 2023 (SAP_BASIS 758) 63 | 64 | ## Note [3315430](https://launchpad.support.sap.com/#/notes/3315430) - PL18 65 | ### /UI2/CL_JSON 66 | * Fixed: handling of cycle references when serializing data and object references. The serialization will stop processing of the reference if it is already in the serialization stack. 67 | * Fixed: performance by serialization of timestamps and UTCLONG fields ([details](https://github.com/SAP/abap-to-json/issues/4)). 68 | * Fixed: performance in class-constructor. 69 | * Fixed: deserialization into typed TYPE REF does not work as expected - always the generic GENERATE approach is used. 70 | * Fixed: null references generated as ABAP_BOOL types 71 | 72 | ## Note [3106267](https://launchpad.support.sap.com/#/notes/3106267) - PL17 73 | ### /UI2/CL_JSON 74 | * Fixed: added support for XSDBOOLEAN Boolean type, to be consistent with CALL TRANSFORMATION id rules. 75 | * Fixed: added support for UTCLONG. This new built-in ABAP data type comes with SAP_BASIS 7.54 and adds native support for timestamps to ABAP. The type is always represented in ISO8601 and does not depend on the TS_AS_ISO8601 parameter. 76 | * Fixed: serialization of the timestamp fields into ISO8601 does not add any more sub-second sections into JSON, while it is always initial for timestamps. 77 | * Fixed: deserialization of the JSON strings with non-breakable spaces (nbsp) 78 | * Fixed: processing of the JSON attribute names with escaped double quotes ("abc efg \\\" etc": "value") 79 | * Fixed: deserialization (generation without typed output structure) does not consider exponential numeric values and tries to transform them into an integer and fails, resulting in 0. 80 | 81 | ### New language features used: 82 | * escape function => min requirement SAP_BASIS 7.31 83 | * find_any_not_of function => min requirement SAP_BASIS 7.02 84 | * FIND/REPLACE PCRE => min requirement SAP_BASIS 7.55 (CE 2008/OP 2020) - delayed 85 | 86 | ## Note [3038042](https://launchpad.support.sap.com/#/notes/3038042) - PL16 87 | ### /UI2/CL_JSON 88 | * Fixed: Serializing of hash tables with empty values and the parameters assoc_arrays_opt and compress produces invalid JSON. 89 | * Fixed: Method serialize produces corrupt data when an initial date or time field is serialized (something like "--" or "::" ). 90 | * Fixed: Performance for serializing and deserializing dynamic data objects (REF TO DATA) is greatly improved. 91 | * New: You can apply text formatting/beautifying to serialized JSON with the new parameter format_output. 92 | * New: You can control the format of how hex values are serialized or deserialized with the new parameter hex_as_base64 for the SERIALIZE and DESERIALIZE methods. If hex_as_base64 is set to abap_true, binary/hex values are processed as base64 (default, compatible behavior). If hex_as_base64 is set to abap_false value is processed in raw hex form. 93 | * New: Now you can provide bool types (true, false), 3 bool types (true, false, undefined), initial timestamp, initial time, and initial date (JSON value for initial ABAP value) in the instance constructor. This would allow you to not inherit class but just parametrize (better performance). 94 | 95 | ## Note [2944398](https://launchpad.support.sap.com/#/notes/2944398 ) - PL15 96 | ### /UI2/CL_JSON 97 | * Fixed. Generating ABAP structures for JSON attributes that include special characters like "/\:;~.,-+=><|()[]{}@+\*?!&$#%^'§\`" fails. 98 | * Fixed. Edm.Guid with the mixed or lowercase is not recognized. 99 | * Fixed. Iterating and data assignments for the generated data object (REF TO DATA), produced by the GENERATE and DESERIALIZE methods, fail in ABAP Cloud. 100 | 101 | ### /UI2/CL_DATA_ACCESS 102 | * Fixed. Index access to generated tables in LOOP construction fails. 103 | 104 | ## Note [2904870](https://launchpad.support.sap.com/#/notes/2904870 ) - PL14 105 | ### /UI2/CL_JSON 106 | * Fixed. Unescaping of strings with a single Unicode entity (e.g., "\uXXXX") does not work 107 | * New. More robust logic for handling invalid JSON (e.g, cases with extra "," without further element { "a": 1, } ) 108 | 109 | ## Note [2870163](https://launchpad.support.sap.com/#/notes/2870163 ) - PL13 110 | ### /UI2/CL_JSON 111 | * Fixed. Conversion exists and does not work when data is located inside internal tables. 112 | * Fixed. TIMESTAMPL subsecond values are truncated when deserializing from Edm.DateTime. 113 | 114 | ## Note [2798102](https://launchpad.support.sap.com/#/notes/2798102 ) - PL12 115 | ### /UI2/CL_JSON 116 | * New. DESERIALIZE and GENERATE methods supporting decoding of Unicode symbols (\u001F) 117 | * Fixed. Invalid JSON causing exception and dump. 118 | ### /UI2/CL_DATA_ACCESS 119 | * Fixed. Access to fields with special characters in the name (e.g., "/BIC/YEAR") fails. 120 | 121 | ## Note [2786259](https://launchpad.support.sap.com/#/notes/2786259 ) - PL11 122 | ### /UI2/CL_JSON 123 | * Optimized. Performance lost, introduced in PL10 (note 2763854) when unescaping special characters (\r\n\t\") 124 | * Fixed. Short dump, with when running the GENERATE method with empty or invalid input 125 | 126 | ### /UI2/CL_DATA_ACCESS 127 | * Fixed. Short dump, when accessing elements of a null array 128 | 129 | ## Note [2763854](https://launchpad.support.sap.com/#/notes/2763854) - PL10 130 | ### /UI2/CL_JSON 131 | * Fixed: Deserialization and generation of the ABAP data from JSON strings with Unicode characters fail 132 | * Fixed: Unescaping of \\t and \\n char combinations in strings handled incorrectly 133 | * Fixed: GENERATE method fails on JSON attribute names containing spaces 134 | 135 | ## Note [2650040](https://launchpad.support.sap.com/#/notes/2650040) 136 | ### /UI2/CL_JSON 137 | * New: Support for deserialization of OData Edm.Guid 138 | * New: Support for Enum data types in ABAP. From SAP_BASIS 7.51, below, the enums are ignored. 139 | * New: Support for conversion exits for serializing and deserializing. 140 | * Fixed: SERIALIZE method delivers an invalid JSON string when NUMC type, filled with spaces, is used. 141 | 142 | ## Note [2629179](https://launchpad.support.sap.com/#/notes/2629179) 143 | ### /UI2/CL_JSON 144 | * New: JSON timestamp fields, serialized in OData Edm.DateTime format (e.g. "\/Date(1467981296000)\/") is supported, and properly deserialized in ABAP date, time, or timestamp fields 145 | * New: JSON timestamp fields, serialized in OData Edm.Time format (e.g., "PT10H34M55S") is supported, and properly deserialized in ABAP date, time, or timestamp fields 146 | * Fixed: content is scrambled when using the GENERATE method for JSON objects with a name containing special characters (for example "__metadata") 147 | * Fixed: GENERATE method does not consider custom name mapping pairs passed as a parameter for CONSTRUCTOR or GENERATE methods 148 | * Fixed: generation of very long integers (serialized numeric date) fails, due to I type overflow (you get 0 instead of the expected number) 149 | 150 | ## Note [2526405](https://launchpad.support.sap.com/#/notes/2526405) 151 | ### /UI2/CL_JSON 152 | * Fixed: Deserialization of the inconsistent data (JSON string into ABAP table) leads to a short dump if the JSON string is empty. 153 | * Fixed: Serialization of data with includes defined suffix (RENAME WITH SUFFIX) dumps 154 | * Fixed: GENERATE method fails if the JSON object contains duplicate attributes and PRETTY_MODE-CAMEL_CASE is not used. 155 | * Fixed: GENERATE method fails if JSON object contains attribute names longer than 30 characters (allowed ABAP field length). This can also occur in case the name is shorter than 30 characters, but PRETTY_MODE-CAMEL_CASE is used. 156 | * New: methods DUMP_INT, DUMP_TYPE, RESTORE_TYPE, and RESTORE can be overridden now. So, you can introduce your data type conversion on serialization and deserialization. 157 | * New: now it is possible to pass the name mapping table as a parameter for the constructor/serialize/deserialize and control the way JSON names are formatted/mapped to ABAP names. This may help if you need special rules for name formatting (for special characters or two long JSON attributes) and standard pretty printing modes cannot help. With this feature, you may eliminate the need for the class extension and redefine the PRETTY_NAME and PRETTY_NAME_EX methods. 158 | * New: The PRETTY_NAME_EX method was extended to support the encoding of more special characters (characters needed in JSON names but can not be used as part of the ABAP name). The supported characters are: "!#$%&\*-~/:|@.". Used with pretty_mode-extended. 159 | 160 | ### /UI2/CL_DATA_ACCESS 161 | * New: /UI2/CL_DATA_ACCESS class for working with dynamic ABAP data object (generated with method /UI2/CL_JSON=>GENERATE). The class can be used as a replacement for multiple ASSIGN COMPONENT language constructions. 162 | 163 | ## Note [2292558](https://launchpad.support.sap.com/#/notes/2292558) 164 | * Fixed: Empty JSON objects, serialized as entries of the table, are not deserialized into corresponding ABAP structures, and further parsing of the JSON string after an empty object is skipped. 165 | * Fixed: JSON fields containing stringified timestamp representation in ISO 8601 format are not deserialized properly in the corresponding ABAP timestamp field. 166 | 167 | ## Note [2300508](https://launchpad.support.sap.com/#/notes/2300508) 168 | * Fixed: Recursive (hierarchical) JSON objects cannot be deserialized. 169 | 170 | ## Note [2330592](https://launchpad.support.sap.com/#/notes/2330592) 171 | * Fixed: Partial serialization/deserialization of the JSON is not supported 172 | * New: Extending the class is supported 173 | * New: Added support for serializing named include structures from ABAP as embedded sub-objects in JSON 174 | 175 | ## Note [2368774](https://launchpad.support.sap.com/#/notes/2368774) 176 | * Fixed: /UI2/CL_JSON creates unnecessary wrapping JSON object around value for name/value (table with 2 fields), tables with 1 unique key 177 | * Fixed: Performance of serialization/deserialization of big tables into/from JSON associative arrays (maps) is slow 178 | * Fixed: When trying to deserialize an invalid (not matching) structure from JSON to ABAP dump OBJECTS_MOVE_NOT_SUPPORTED occurs 179 | 180 | ## Note [2382783](https://launchpad.support.sap.com/#/notes/2382783) 181 | * Fixed: Unescape of symbol '\' on JSON deserialization does not work 182 | * Fixed: Short dump on serialization of classes with protected/private attributes 183 | * Fixed: Short dump when serializing dynamic, non-existing types 184 | 185 | ## Note [2429758](https://launchpad.support.sap.com/#/notes/2429758) 186 | * Fixed: Short Dump on deserialization of classes with read-only attributes 187 | * New: The serialization parameter was added to NUMC_AS_STRING, controlling the way NUMC fields are serialized. The default is FALSE. If set to TRUE, NUMC fields are serialized not as numbers, but as strings, with all leading zeroes. Deserialization works compatibly with both ways of NUMC serialized data. 188 | * New: GENERATE and GENERATE_INT methods are added for the on-the-fly creation of ABAP data objects from JSON, without the need to have a predefined ABAP structure. Supports automatic creation of ABAP structures, tables, and elementary types, concerning JSON types. Supports structure/table nesting. 189 | * New: DESERIALIZE_INT method throws an exception CX_SY_MOVE_CAST_ERROR and stops further processing in case of malformed data found, and the STRICT_MODE parameter in the constructor is set to TRUE. 190 | * New: Support for XSTRING was added as input for deserialization. 191 | 192 | ## Note [2480119](https://launchpad.support.sap.com/#/notes/2480119) 193 | *Last note with support of SAP_BASIS 740* 194 | * New: GENERATE method creates a local custom class for deserialization (lc_json_custom), instead of standard /ui2/cl_json 195 | * Fixed: Internal tables are not initialized when deserializing JSON with empty arrays 196 | * New: Deserialization into a field with REF TO data type, if the field is bound, using a referenced data type 197 | * New: Deserialization uses automatic generation of the data if the field has a "REF TO DATA" type and the bound data is initial 198 | -------------------------------------------------------------------------------- /docs/images/generate_sbook1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/abap-to-json/2e150ae959695555d45fae70b470f56298e560c2/docs/images/generate_sbook1.png -------------------------------------------------------------------------------- /docs/images/generate_sbook2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/abap-to-json/2e150ae959695555d45fae70b470f56298e560c2/docs/images/generate_sbook2.png -------------------------------------------------------------------------------- /docs/images/generate_sbook_optimized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/abap-to-json/2e150ae959695555d45fae70b470f56298e560c2/docs/images/generate_sbook_optimized.png -------------------------------------------------------------------------------- /src/package.devc.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Open Source version of /UI2/CL_JSON 7 | X 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/z_ui2_data_access.clas.abap: -------------------------------------------------------------------------------- 1 | "! Helper Class for /UI2/CL_JSON 2 | "! Usage examples and documentation can be found on https://github.com/SAP/abap-to-json 3 | class Z_UI2_DATA_ACCESS definition 4 | public 5 | create public . 6 | 7 | public section. 8 | type-pools ABAP . 9 | 10 | class-methods CLASS_CONSTRUCTOR . 11 | class-methods CREATE 12 | importing 13 | !IR_DATA type ref to DATA optional 14 | !IV_DATA type DATA optional 15 | !IV_COMPONENT type STRING optional 16 | returning 17 | value(RO_REF) type ref to Z_UI2_DATA_ACCESS . 18 | methods CONSTRUCTOR 19 | importing 20 | !IR_DATA type ref to DATA optional 21 | !IV_DATA type DATA optional . 22 | methods AT 23 | importing 24 | !IV_COMPONENT type STRING optional 25 | returning 26 | value(RO_REF) type ref to Z_UI2_DATA_ACCESS . 27 | methods EMPTY 28 | returning 29 | value(RV_VAL) type ABAP_BOOL . 30 | methods REF 31 | returning 32 | value(RV_DATA) type ref to DATA . 33 | methods VALUE 34 | importing 35 | !IV_DEFAULT type DATA optional 36 | exporting 37 | !EV_DATA type DATA . 38 | methods SET 39 | importing 40 | !IV_DATA type DATA 41 | returning 42 | value(RV_SUCCESS) type ABAP_BOOL . 43 | methods COUNT 44 | returning 45 | value(RV_LINES) type I . 46 | protected section. 47 | 48 | data MR_DATA type ref to DATA . 49 | 50 | class-methods DEREF 51 | importing 52 | !IR_DATA type ref to DATA 53 | returning 54 | value(RR_DATA) type ref to DATA . 55 | methods AT_INT 56 | importing 57 | !IV_COMPONENT type STRING optional 58 | !IV_INDEX type I optional 59 | !IV_KEYS type STRING optional 60 | returning 61 | value(RO_REF) type ref to Z_UI2_DATA_ACCESS . 62 | private section. 63 | 64 | class-data SO_REGEX_HIER type ref to CL_ABAP_REGEX . 65 | class-data SO_REGEX_KEYS type ref to CL_ABAP_REGEX . 66 | ENDCLASS. 67 | 68 | 69 | 70 | CLASS Z_UI2_DATA_ACCESS IMPLEMENTATION. 71 | 72 | 73 | METHOD AT. 74 | 75 | DATA: 76 | lv_component TYPE string, 77 | lv_sindex TYPE string, 78 | lv_keys TYPE string, 79 | lv_index TYPE i, 80 | lt_hier TYPE match_result_tab. 81 | 82 | FIELD-SYMBOLS: 83 | LIKE LINE OF lt_hier, 84 | TYPE LINE OF submatch_result_tab. 85 | 86 | " (?:(\w+)|^)(?:\[(?:(\d+)|([^\]]+))\])? - no check for separators 87 | FIND ALL OCCURRENCES OF REGEX so_regex_hier IN iv_component RESULTS lt_hier. 88 | 89 | ro_ref = me. 90 | 91 | LOOP AT lt_hier ASSIGNING . 92 | CHECK ro_ref->empty( ) EQ abap_false. 93 | READ TABLE -submatches INDEX 1 ASSIGNING . 94 | IF -length IS INITIAL. 95 | CLEAR lv_component. 96 | ELSE. 97 | lv_component = iv_component+-offset(-length). 98 | TRANSLATE lv_component TO UPPER CASE. 99 | ENDIF. 100 | READ TABLE -submatches INDEX 2 ASSIGNING . 101 | IF -length IS INITIAL. 102 | CLEAR lv_index. 103 | ELSE. 104 | lv_index = lv_sindex = iv_component+-offset(-length). 105 | ENDIF. 106 | READ TABLE -submatches INDEX 3 ASSIGNING . 107 | IF -length IS INITIAL. 108 | CLEAR lv_keys. 109 | ELSE. 110 | lv_keys = iv_component+-offset(-length). 111 | ENDIF. 112 | ro_ref = ro_ref->at_int( iv_component = lv_component iv_index = lv_index iv_keys = lv_keys ). 113 | ENDLOOP. 114 | 115 | ENDMETHOD. "at_int 116 | 117 | 118 | METHOD AT_INT. 119 | 120 | DATA: lv_key TYPE string, 121 | lv_value TYPE string, 122 | lt_keys TYPE match_result_tab, 123 | lr_res TYPE REF TO data, 124 | lr_data TYPE REF TO data, 125 | lo_type TYPE REF TO cl_abap_typedescr. 126 | 127 | FIELD-SYMBOLS: TYPE data, 128 | TYPE data, 129 | LIKE LINE OF lt_keys, 130 | TYPE LINE OF submatch_result_tab, 131 |
TYPE ANY TABLE, 132 | TYPE INDEX TABLE. 133 | 134 | IF mr_data IS BOUND. 135 | IF iv_component IS NOT INITIAL. 136 | ASSIGN mr_data->* TO . 137 | ASSIGN COMPONENT iv_component OF STRUCTURE TO . 138 | IF IS ASSIGNED. 139 | GET REFERENCE OF INTO lr_data. 140 | lr_res = deref( lr_data ). 141 | ENDIF. 142 | ELSE. 143 | lr_res = mr_data. 144 | ENDIF. 145 | ENDIF. 146 | 147 | IF lr_res IS NOT INITIAL AND ( iv_index IS NOT INITIAL OR iv_keys IS NOT INITIAL ). 148 | lo_type = cl_abap_typedescr=>describe_by_data_ref( lr_res ). 149 | IF lo_type->kind EQ cl_abap_typedescr=>kind_table. 150 | " check for table index access 151 | IF iv_index IS NOT INITIAL. 152 | ASSIGN lr_res->* TO . 153 | IF sy-subrc IS INITIAL. 154 | READ TABLE INDEX iv_index REFERENCE INTO lr_res. 155 | IF sy-subrc IS NOT INITIAL. 156 | CLEAR lr_res. 157 | ENDIF. 158 | ELSE. 159 | CLEAR lr_res. 160 | ENDIF. 161 | ELSE. " iv_keys IS NOT INITIAL 162 | ASSIGN lr_res->* TO
. 163 | IF sy-subrc IS INITIAL. 164 | CREATE DATA lr_data LIKE LINE OF
. 165 | ASSIGN lr_data->* TO . 166 | FIND ALL OCCURRENCES OF REGEX so_regex_keys IN iv_keys RESULTS lt_keys. 167 | IF sy-subrc IS INITIAL. 168 | LOOP AT lt_keys ASSIGNING . 169 | READ TABLE -submatches INDEX 1 ASSIGNING . 170 | lv_key = iv_keys+-offset(-length). 171 | TRANSLATE lv_key TO UPPER CASE. 172 | READ TABLE -submatches INDEX 2 ASSIGNING . 173 | lv_value = iv_keys+-offset(-length). 174 | ASSIGN COMPONENT lv_key OF STRUCTURE TO . 175 | CHECK sy-subrc IS INITIAL. 176 | = lv_value. 177 | ENDLOOP. 178 | ELSE. 179 | = lv_key. 180 | ENDIF. 181 | READ TABLE
FROM REFERENCE INTO lr_res. 182 | IF sy-subrc IS NOT INITIAL. 183 | CLEAR lr_res. 184 | ENDIF. 185 | ELSE. 186 | CLEAR lr_res. 187 | ENDIF. 188 | ENDIF. 189 | ENDIF. 190 | ENDIF. 191 | 192 | CREATE OBJECT ro_ref 193 | EXPORTING 194 | ir_data = lr_res. 195 | 196 | ENDMETHOD. "at_int 197 | 198 | 199 | METHOD CONSTRUCTOR. 200 | IF ir_data IS NOT INITIAL. 201 | mr_data = ir_data. 202 | ELSEIF iv_data IS SUPPLIED. 203 | GET REFERENCE OF iv_data INTO mr_data. 204 | ENDIF. 205 | mr_data = deref( mr_data ). 206 | ENDMETHOD. "constructor 207 | 208 | 209 | METHOD COUNT. 210 | 211 | FIELD-SYMBOLS: TYPE ANY TABLE. 212 | 213 | IF mr_data IS BOUND. 214 | ASSIGN mr_data->* TO . 215 | IF sy-subrc IS INITIAL. 216 | rv_lines = lines( ). 217 | ENDIF. 218 | ENDIF. 219 | 220 | ENDMETHOD. 221 | 222 | 223 | METHOD CREATE. 224 | IF iv_data IS SUPPLIED. 225 | CREATE OBJECT ro_ref 226 | EXPORTING 227 | iv_data = iv_data. 228 | ELSE. 229 | CREATE OBJECT ro_ref 230 | EXPORTING 231 | ir_data = ir_data. 232 | ENDIF. 233 | 234 | IF iv_component IS NOT INITIAL. 235 | ro_ref = ro_ref->at( iv_component ). 236 | ENDIF. 237 | ENDMETHOD. "create 238 | 239 | 240 | METHOD DEREF. 241 | 242 | DATA: lo_type TYPE REF TO cl_abap_typedescr. 243 | 244 | FIELD-SYMBOLS: TYPE data. 245 | 246 | rr_data = ir_data. 247 | IF rr_data IS NOT INITIAL. 248 | lo_type = cl_abap_typedescr=>describe_by_data_ref( ir_data ). 249 | IF lo_type->kind EQ cl_abap_typedescr=>kind_ref. 250 | ASSIGN ir_data->* TO . 251 | rr_data = deref( ). 252 | ENDIF. 253 | ENDIF. 254 | 255 | ENDMETHOD. "deref 256 | 257 | 258 | METHOD EMPTY. 259 | IF mr_data IS INITIAL. 260 | rv_val = abap_true. 261 | ENDIF. 262 | ENDMETHOD. "empty 263 | 264 | 265 | METHOD REF. 266 | rv_data = mr_data. 267 | ENDMETHOD. "ref 268 | 269 | 270 | METHOD SET. 271 | 272 | FIELD-SYMBOLS: TYPE data. 273 | 274 | IF mr_data IS BOUND. 275 | ASSIGN mr_data->* TO . 276 | = iv_data. 277 | rv_success = abap_true. 278 | ENDIF. 279 | 280 | ENDMETHOD. 281 | 282 | 283 | METHOD VALUE. 284 | 285 | DATA: lo_type_out TYPE REF TO cl_abap_typedescr, 286 | lo_type_in TYPE REF TO cl_abap_typedescr. 287 | 288 | FIELD-SYMBOLS: TYPE data. 289 | 290 | CLEAR ev_data. 291 | 292 | IF mr_data IS BOUND. 293 | ASSIGN mr_data->* TO . 294 | lo_type_out = cl_abap_typedescr=>describe_by_data( ev_data ). 295 | lo_type_in = cl_abap_typedescr=>describe_by_data( ). 296 | IF lo_type_out->kind EQ lo_type_in->kind. 297 | ev_data = . 298 | ENDIF. 299 | ELSEIF iv_default IS SUPPLIED. 300 | ev_data = iv_default. 301 | ENDIF. 302 | 303 | ENDMETHOD. "value 304 | 305 | 306 | METHOD class_constructor. 307 | create_regexp so_regex_hier '(?:([\w\/]+)|^)(?:\[\s*(?:(\d+)|([^\]]+))\s*\])?(?:(?:-\>)|(?:-)|(?:=>)|$)'. 308 | create_regexp so_regex_keys '(\w+)\s*=\s*([^,]*),?'. 309 | ENDMETHOD. 310 | ENDCLASS. 311 | -------------------------------------------------------------------------------- /src/z_ui2_data_access.clas.macros.abap: -------------------------------------------------------------------------------- 1 | *"* use this source file for any macro definitions you need 2 | *"* in the implementation part of the class 3 | 4 | DEFINE create_regexp. 5 | " first try PCRE 6 | TRY. 7 | CALL METHOD cl_abap_regex=>('CREATE_PCRE') 8 | EXPORTING 9 | pattern = &2 10 | RECEIVING 11 | regex = &1. "#EC NOTEXT 12 | CATCH cx_sy_dyn_call_error. 13 | CREATE OBJECT &1 14 | EXPORTING 15 | pattern = &2 ##REGEX_POSIX ##NO_TEXT. "#EC NOTEXT 16 | ENDTRY. 17 | END-OF-DEFINITION. 18 | -------------------------------------------------------------------------------- /src/z_ui2_data_access.clas.testclasses.abap: -------------------------------------------------------------------------------- 1 | *"* use this source file for your ABAP unit test classes 2 | CLASS lc_ut DEFINITION FOR TESTING FINAL "#AU Duration Medium 3 | "#AU Risk_Level Harmless 4 | INHERITING FROM z_ui2_data_access. 5 | 6 | PUBLIC SECTION. 7 | TYPES: 8 | BEGIN OF t_properties, 9 | enabled TYPE abap_bool, 10 | length TYPE i, 11 | description TYPE c LENGTH 20, 12 | END OF t_properties, 13 | BEGIN OF t_data, 14 | id TYPE i, 15 | properties TYPE t_properties, 16 | content TYPE string, 17 | END OF t_data, 18 | BEGIN OF t_name_value, 19 | name TYPE string, 20 | value TYPE string, 21 | END OF t_name_value, 22 | BEGIN OF t_example, 23 | flag TYPE abap_bool, 24 | /bic/z_year TYPE i, 25 | props TYPE t_data, 26 | params TYPE SORTED TABLE OF t_name_value WITH UNIQUE KEY name, 27 | params2 TYPE SORTED TABLE OF t_name_value WITH UNIQUE KEY name value, 28 | END OF t_example. 29 | 30 | METHODS: test_basic_actions FOR TESTING. 31 | METHODS: test_table_access FOR TESTING. 32 | METHODS: test_table_access2 FOR TESTING. 33 | METHODS: test_table_access3 FOR TESTING. 34 | 35 | ENDCLASS. "lc_ut 36 | 37 | 38 | CLASS lc_ut IMPLEMENTATION. 39 | 40 | METHOD test_basic_actions. 41 | 42 | DATA: ls_data TYPE t_example, 43 | lr_data TYPE REF TO data, 44 | lr_ref TYPE REF TO data, 45 | lv_int TYPE i, 46 | lo_data TYPE REF TO z_ui2_data_access, 47 | lo_tmp LIKE lo_data. 48 | 49 | FIELD-SYMBOLS: TYPE data. 50 | 51 | ls_data-props-id = 12345. 52 | ls_data-/bic/z_year = 1978. 53 | ls_data-props-content = `Some Content`. 54 | ls_data-props-properties-enabled = abap_false. 55 | ls_data-props-properties-length = 10. 56 | ls_data-props-properties-description = `My description`. 57 | 58 | GET REFERENCE OF ls_data INTO lr_data. 59 | 60 | CREATE OBJECT lo_data EXPORTING ir_data = lr_data. 61 | 62 | " standard way (does not work on SAP_BASIS 700) 63 | lo_tmp = lo_data->at( `PROPS` ). 64 | lo_tmp = lo_tmp->at( `id` ). 65 | lr_ref = lo_tmp->ref( ). 66 | " lr_ref = lo_data->at(`PROPS`)->at(`id`)->ref( ). => 700 67 | cl_aunit_assert=>assert_bound( act = lr_ref msg = `Dynamic access to existing property fails!` ). 68 | 69 | ASSIGN lr_ref->* TO . 70 | cl_aunit_assert=>assert_equals( act = exp = ls_data-props-id msg = `Dynamic access to existing property fails!` ). 71 | 72 | lo_tmp = lo_data->at( `PROPS` ). 73 | lo_tmp = lo_tmp->at( `key` ). 74 | lr_ref = lo_tmp->ref( ). 75 | " lr_ref = lo_data->at(`PROPS`)->at(`key`)->ref( ). => 700 76 | cl_aunit_assert=>assert_not_bound( act = lr_ref msg = `Dynamic access to NOT existing property fails!` ). 77 | 78 | " XPath like 79 | lo_tmp = lo_data->at( `PROPS-ID` ). 80 | lr_ref = lo_tmp->ref( ). 81 | " lr_ref = lo_data->at(`PROPS-ID`)->ref( ). => 700 82 | cl_aunit_assert=>assert_bound( act = lr_ref msg = `Dynamic access to existing property fails!` ). 83 | 84 | ASSIGN lr_ref->* TO . 85 | cl_aunit_assert=>assert_equals( act = exp = ls_data-props-id msg = `Dynamic access to existing property fails!` ). 86 | 87 | " Alternative component separator 88 | lo_tmp = lo_data->at( `PROPS->ID` ). 89 | lr_ref = lo_tmp->ref( ). 90 | " lr_ref = lo_data->at(`PROPS->ID`)->ref( ). => 700 91 | cl_aunit_assert=>assert_bound( act = lr_ref msg = `Dynamic access to existing property fails!` ). 92 | 93 | ASSIGN lr_ref->* TO . 94 | cl_aunit_assert=>assert_equals( act = exp = ls_data-props-id msg = `Dynamic access to existing property fails!` ). 95 | 96 | " access for attributes with special characters 97 | lo_tmp = lo_data->at( `/BIC/Z_YEAR` ). 98 | lr_ref = lo_tmp->ref( ). 99 | " lr_ref = lo_data->at(`PROPS-ID`)->ref( ). => 700 100 | cl_aunit_assert=>assert_bound( act = lr_ref msg = `Dynamic access to existing property with special characters fails!` ). 101 | 102 | ASSIGN lr_ref->* TO . 103 | cl_aunit_assert=>assert_equals( act = exp = ls_data-/bic/z_year msg = `Dynamic access to existing property with special characters fails!` ). 104 | 105 | " using helper method for creation 106 | lo_tmp = create( ir_data = lr_data iv_component = `PROPS-ID` ). 107 | lr_ref = lo_tmp->ref( ). 108 | " lr_ref = create( ir_data = lr_data iv_component = `PROPS-ID`)->ref( ). => 700 109 | cl_aunit_assert=>assert_bound( act = lr_ref msg = `Dynamic access to existing property fails!` ). 110 | 111 | ASSIGN lr_ref->* TO . 112 | cl_aunit_assert=>assert_equals( act = exp = ls_data-props-id msg = `Dynamic access to existing property fails!` ). 113 | 114 | " reading value 115 | lo_tmp = lo_data->at( `props-properties-length` ). 116 | lo_tmp->value( IMPORTING ev_data = lv_int ). 117 | " lo_data->at(`props-properties-length`)->value( IMPORTING ev_data = lv_int ). => 700 118 | cl_aunit_assert=>assert_equals( act = lv_int exp = ls_data-props-properties-length msg = `Dynamic read of existing the property fails!` ). 119 | 120 | lv_int = 25. 121 | lo_tmp = lo_data->at( `props-properties-len` ). 122 | lo_tmp->value( IMPORTING ev_data = lv_int ). 123 | " lo_data->at(`props-properties-len`)->value( IMPORTING ev_data = lv_int ). => 700 124 | cl_aunit_assert=>assert_initial( act = lv_int msg = `Dynamic read of the NOT existing property fails!` ). 125 | 126 | " modifing value 127 | lv_int = 25. 128 | lo_tmp = lo_data->at( `props-properties-length` ). 129 | lr_ref = lo_tmp->ref( ). 130 | " lr_ref = lo_data->at(`props-properties-length`)->ref( ). => 700 131 | ASSIGN lr_ref->* TO . 132 | = lv_int. 133 | cl_aunit_assert=>assert_equals( act = lv_int exp = ls_data-props-properties-length msg = `Dynamic modification of the existing property fails!` ). 134 | 135 | lv_int = 15. 136 | lo_tmp = lo_data->at( `props-properties-length` ). 137 | lo_tmp->set( lv_int ). 138 | " lo_data->at(`props-properties-length`)->set( lv_int ). => 700 139 | cl_aunit_assert=>assert_equals( act = lv_int exp = ls_data-props-properties-length msg = `Dynamic modification of the existing property fails!` ). 140 | 141 | " degenerated example 142 | lv_int = 15. 143 | lo_tmp = create( iv_data = lv_int ). 144 | lr_ref = lo_tmp->ref( ). 145 | " lr_ref = create( iv_data = lv_int )->ref( ). => 700 146 | ASSIGN lr_ref->* TO . 147 | cl_aunit_assert=>assert_equals( act = exp = lv_int msg = `Dynamic access of existing the property fails!` ). 148 | 149 | ENDMETHOD. 150 | 151 | METHOD test_table_access. 152 | 153 | DATA: 154 | ls_data TYPE t_example, 155 | ls_line LIKE LINE OF ls_data-params, 156 | lv_value TYPE string, 157 | lo_data TYPE REF TO z_ui2_data_access. 158 | 159 | ls_line-name = `KEY1`. 160 | ls_line-value = `Value1`. 161 | INSERT ls_line INTO TABLE ls_data-params. 162 | INSERT ls_line INTO TABLE ls_data-params2. 163 | 164 | ls_line-name = `KEY2`. 165 | ls_line-value = `Value1`. 166 | INSERT ls_line INTO TABLE ls_data-params. 167 | INSERT ls_line INTO TABLE ls_data-params2. 168 | 169 | ls_line-name = `KEY2`. 170 | ls_line-value = `Value2`. 171 | INSERT ls_line INTO TABLE ls_data-params. 172 | INSERT ls_line INTO TABLE ls_data-params2. 173 | 174 | ls_line-name = `KEY3`. 175 | ls_line-value = `Value1, Value2`. 176 | INSERT ls_line INTO TABLE ls_data-params. 177 | INSERT ls_line INTO TABLE ls_data-params2. 178 | 179 | lo_data = create( iv_data = ls_data iv_component = `params[2]-name` ). 180 | lo_data->value( IMPORTING ev_data = lv_value ). 181 | 182 | cl_aunit_assert=>assert_equals( act = lv_value exp = `KEY2` msg = `Dynamic read to table item by index fails!` ). 183 | 184 | lo_data = create( iv_data = ls_data iv_component = `params[name=KEY1]-value` ). 185 | lo_data->value( IMPORTING ev_data = lv_value ). 186 | 187 | cl_aunit_assert=>assert_equals( act = lv_value exp = `Value1` msg = `Dynamic read to table item by key index fails!` ). 188 | 189 | lo_data = create( iv_data = ls_data iv_component = `params` ). 190 | lo_data = lo_data->at( `[name=KEY1]-value` ). 191 | lo_data->value( IMPORTING ev_data = lv_value ). 192 | 193 | cl_aunit_assert=>assert_equals( act = lv_value exp = `Value1` msg = `Dynamic read to table item by key index fails!` ). 194 | 195 | lo_data = create( iv_data = ls_data iv_component = `params2[name=KEY2, value=Value2]-value` ). 196 | lo_data->value( IMPORTING ev_data = lv_value ). 197 | 198 | cl_aunit_assert=>assert_equals( act = lv_value exp = `Value2` msg = `Dynamic read to table item by key index fails!` ). 199 | 200 | " we do not support usage usage of "," or "]" as part of the key :( 201 | lo_data = create( iv_data = ls_data iv_component = `params2[name=KEY3, value=Value1, Value2]-value` ). 202 | lo_data->value( IMPORTING ev_data = lv_value ). 203 | 204 | cl_aunit_assert=>assert_equals( act = lv_value exp = `` msg = `Dynamic read to table item by unescaped key works!` ). 205 | 206 | ENDMETHOD. 207 | 208 | METHOD test_table_access2. 209 | 210 | DATA: lr_data TYPE REF TO data, 211 | lr_ref TYPE REF TO data, 212 | lo_data TYPE REF TO z_ui2_data_access, 213 | lv_json TYPE z_ui2_json=>json. 214 | 215 | lv_json = `{"type": "UPDATE","organization": {"duns": "429773946"},"elements": [{"element": "organization","current": null,"timestamp": "2019-05-01T19:56:20Z"}]}`. 216 | lr_data = z_ui2_json=>generate( json = lv_json ). 217 | lo_data = create( ir_data = lr_data iv_component = 'elements[1]-current[1]-value' ). 218 | lr_ref = lo_data->ref( ). 219 | 220 | cl_aunit_assert=>assert_not_bound( act = lr_ref msg = `Access to null array fails!` ). 221 | 222 | ENDMETHOD. 223 | 224 | METHOD test_table_access3. 225 | 226 | DATA: access TYPE REF TO z_ui2_data_access, 227 | itab TYPE REF TO z_ui2_data_access, 228 | value TYPE REF TO z_ui2_data_access, 229 | lr_data TYPE REF TO data, 230 | index_act TYPE string, 231 | index_exp TYPE string, 232 | lv_key TYPE string, 233 | json TYPE z_ui2_json=>json. 234 | 235 | CONCATENATE 236 | `{ "SSDC": {` 237 | `"checks_for_new_checkset": [` 238 | `{ "check_name": "OLC_1", "sort_order": "1", "mandatory": "X" },` 239 | `{ "check_name": "OLC_2", "sort_order": "2", "mandatory": " " },` 240 | `{ "check_name": "OLC_3", "sort_order": "3", "mandatory": "X" } ] } }` 241 | INTO json. 242 | 243 | lr_data = z_ui2_json=>generate( json = json ). 244 | access = create( ir_data = lr_data ). 245 | itab = access->at( `SSDC-checks_for_new_checkset` ). 246 | 247 | DO 3 TIMES. 248 | index_exp = sy-index. 249 | CONDENSE index_exp. 250 | CONCATENATE '[' index_exp ']-sort_order' INTO lv_key. 251 | value = itab->at( lv_key ). 252 | value->value( IMPORTING ev_data = index_act ). 253 | cl_aunit_assert=>assert_equals( act = index_act exp = index_exp msg = `Access with index fails!` ). 254 | ENDDO. 255 | 256 | ENDMETHOD. 257 | 258 | ENDCLASS. 259 | -------------------------------------------------------------------------------- /src/z_ui2_data_access.clas.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Z_UI2_DATA_ACCESS 7 | E 8 | Dynamic Data Access Helper Class 9 | 1 10 | X 11 | X 12 | X 13 | X 14 | 15 | 16 | 17 | AT 18 | E 19 | Component accessor 20 | 21 | 22 | AT_INT 23 | E 24 | Internal helper for comonent access 25 | 26 | 27 | CLASS_CONSTRUCTOR 28 | E 29 | CLASS_CONSTRUCTOR 30 | 31 | 32 | CONSTRUCTOR 33 | E 34 | Constructor 35 | 36 | 37 | COUNT 38 | E 39 | Return number of table lines, if attached object is table 40 | 41 | 42 | CREATE 43 | E 44 | Helper method for creating instance of dynamic accessor 45 | 46 | 47 | DEREF 48 | E 49 | Dereference deep nested references 50 | 51 | 52 | EMPTY 53 | E 54 | Returns TRUE if embedded object is initial (not bound) 55 | 56 | 57 | MR_DATA 58 | E 59 | Reference to the data 60 | 61 | 62 | REF 63 | E 64 | Returns reference to embedded object 65 | 66 | 67 | SET 68 | E 69 | Sets value of the embedded object, if not initial 70 | 71 | 72 | SO_REGEX_HIER 73 | E 74 | Cached regexp for splitting pathes 75 | 76 | 77 | SO_REGEX_KEYS 78 | E 79 | Cached regexp for extracting keys 80 | 81 | 82 | VALUE 83 | E 84 | Returns copy of the value 85 | 86 | 87 | 88 | 89 | AT 90 | IV_COMPONENT 91 | E 92 | Sub-component name (xpath is supported) 93 | 94 | 95 | AT 96 | RO_REF 97 | E 98 | Reference to accessor object pointing to subcomponent 99 | 100 | 101 | CONSTRUCTOR 102 | IR_DATA 103 | E 104 | Reference to data (allows modification) 105 | 106 | 107 | CONSTRUCTOR 108 | IV_DATA 109 | E 110 | Data (modification not allowed) 111 | 112 | 113 | COUNT 114 | RV_LINES 115 | E 116 | Lines count 117 | 118 | 119 | CREATE 120 | IR_DATA 121 | E 122 | Reference to data (allows modification) 123 | 124 | 125 | CREATE 126 | IV_COMPONENT 127 | E 128 | Sub-component name (xpath is supported) 129 | 130 | 131 | CREATE 132 | IV_DATA 133 | E 134 | Data (modification not allowed) 135 | 136 | 137 | CREATE 138 | RO_REF 139 | E 140 | Reference to accessor object pointing to subcomponent 141 | 142 | 143 | EMPTY 144 | RV_VAL 145 | E 146 | ABAP_TRUE if object is initial and data is not bound 147 | 148 | 149 | REF 150 | RV_DATA 151 | E 152 | Reference to embedded data 153 | 154 | 155 | SET 156 | IV_DATA 157 | E 158 | New value for embedded object 159 | 160 | 161 | SET 162 | RV_SUCCESS 163 | E 164 | TRUE, if data was successfully modified 165 | 166 | 167 | VALUE 168 | EV_DATA 169 | E 170 | Copy of the embedded data value, or initial 171 | 172 | 173 | VALUE 174 | IV_DEFAULT 175 | E 176 | Default value to be returned, if no data is assigned 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /src/z_ui2_json.clas.locals_imp.abap: -------------------------------------------------------------------------------- 1 | *"* local class implementation for public class 2 | *"* use this source file for the implementation part of 3 | *"* local helper classes 4 | 5 | *----------------------------------------------------------------------* 6 | * CLASS lcl_util DEFINITION 7 | *----------------------------------------------------------------------* 8 | * 9 | *----------------------------------------------------------------------* 10 | 11 | CLASS lcl_util DEFINITION FINAL FRIENDS z_ui2_json. 12 | 13 | PUBLIC SECTION. 14 | CLASS-METHODS: 15 | class_constructor. 16 | 17 | PRIVATE SECTION. 18 | CLASS-DATA: 19 | so_regex_iso8601 TYPE REF TO cl_abap_regex, 20 | so_regex_edm_date_time TYPE REF TO cl_abap_regex. 21 | 22 | CLASS-METHODS: 23 | _escape IMPORTING in TYPE data EXPORTING out TYPE string, 24 | to_md5 IMPORTING iv_value TYPE string RETURNING VALUE(rv_result) TYPE string, 25 | read_string IMPORTING json TYPE string mark TYPE i CHANGING offset TYPE i DEFAULT 0 text TYPE string RAISING cx_sy_move_cast_error, 26 | read_iso8601 IMPORTING in TYPE string RETURNING VALUE(rv_tstm) TYPE timestampl, 27 | read_edm_datetime IMPORTING in TYPE string RETURNING VALUE(rv_tstm) TYPE timestampl, 28 | describe_type IMPORTING io_type_descr TYPE REF TO cl_abap_typedescr RETURNING VALUE(rv_typename) TYPE string. 29 | 30 | ENDCLASS. "lcl_util DEFINITION 31 | 32 | *----------------------------------------------------------------------* 33 | * CLASS lcl_util IMPLEMENTATION 34 | *----------------------------------------------------------------------* 35 | * 36 | *----------------------------------------------------------------------* 37 | CLASS lcl_util IMPLEMENTATION. 38 | 39 | METHOD class_constructor. 40 | 41 | " support for ISO8601 => https://en.wikipedia.org/wiki/ISO_8601 42 | create_regexp so_regex_iso8601 '^(?:(\d{4})-?(\d{2})-?(\d{2}))?(?:T(\d{2}):?(\d{2})(?::?(\d{2}))?(?:[\.,](\d{0,9}))?(?:Z|(?:([+-])(\d{2})(?::?(\d{2}))?))?)?\s*$'. 43 | " support for Edm.DateTime => http://www.odata.org/documentation/odata-version-2-0/json-format/ 44 | create_regexp so_regex_edm_date_time '^\/[Dd][Aa][Tt][Ee]\((-?\d+)(?:([+-])(\d{1,4}))?\)\/\s*$'. 45 | 46 | ENDMETHOD. 47 | 48 | METHOD describe_type. 49 | 50 | DATA: lv_kind_name TYPE string, 51 | lv_pos TYPE i. 52 | 53 | rv_typename = `?`. 54 | 55 | CHECK io_type_descr IS NOT INITIAL. 56 | 57 | FIND FIRST OCCURRENCE OF '\TYPE=' IN io_type_descr->absolute_name MATCH OFFSET lv_pos. 58 | IF sy-subrc IS INITIAL. 59 | lv_pos = lv_pos + 6. 60 | IF io_type_descr->absolute_name+lv_pos(1) NE '%'. 61 | rv_typename = io_type_descr->absolute_name+lv_pos. 62 | ELSE. 63 | CLEAR rv_typename. 64 | ENDIF. 65 | ELSE. 66 | rv_typename = io_type_descr->absolute_name. 67 | ENDIF. 68 | 69 | CASE io_type_descr->kind. 70 | WHEN cl_abap_typedescr=>kind_table. 71 | lv_kind_name = `TABLE`. 72 | WHEN cl_abap_typedescr=>kind_struct. 73 | lv_kind_name = `STRUCTURE`. 74 | WHEN cl_abap_typedescr=>kind_class. 75 | lv_kind_name = `CLASS`. 76 | WHEN cl_abap_typedescr=>kind_intf. 77 | lv_kind_name = `INTERFACE`. 78 | WHEN cl_abap_typedescr=>kind_ref. 79 | lv_kind_name = `REFERENCE`. 80 | ENDCASE. 81 | 82 | IF lv_kind_name IS NOT INITIAL. 83 | IF rv_typename IS NOT INITIAL. 84 | CONCATENATE lv_kind_name `(` rv_typename `)` INTO rv_typename. 85 | ELSE. 86 | rv_typename = lv_kind_name. 87 | ENDIF. 88 | ENDIF. 89 | 90 | ENDMETHOD. 91 | 92 | METHOD read_string. 93 | 94 | DATA: match LIKE offset, 95 | pos LIKE offset. 96 | 97 | " ASSERT json+offset(1) EQ `"`. 98 | 99 | DO. 100 | FIND FIRST OCCURRENCE OF `"` IN SECTION OFFSET offset OF json MATCH OFFSET pos. 101 | IF sy-subrc IS NOT INITIAL. 102 | throw_error. 103 | ENDIF. 104 | 105 | offset = pos. 106 | pos = pos - 1. 107 | " if escaped search further 108 | WHILE json+pos(1) EQ `\`. 109 | pos = pos - 1. 110 | ENDWHILE. 111 | match = ( offset - pos ) MOD 2. 112 | IF match NE 0. 113 | EXIT. 114 | ENDIF. 115 | offset = offset + 1. 116 | ENDDO. 117 | 118 | match = offset - mark. 119 | text = json+mark(match). 120 | 121 | ENDMETHOD. "read_string 122 | 123 | METHOD read_iso8601. 124 | 125 | DATA: offset_sign TYPE c, 126 | offset_hours TYPE c LENGTH 2, 127 | offset_minutes TYPE c LENGTH 2, 128 | stimestmp TYPE c LENGTH 22, 129 | seconds TYPE i. 130 | 131 | FIND FIRST OCCURRENCE OF REGEX so_regex_iso8601 IN in SUBMATCHES stimestmp stimestmp+4 stimestmp+6 stimestmp+8 stimestmp+10 stimestmp+12 stimestmp+15 offset_sign offset_hours offset_minutes. 132 | CHECK sy-subrc IS INITIAL. 133 | 134 | IF stimestmp+15(1) IS NOT INITIAL. " msec provided 135 | stimestmp+14(1) = '.'. 136 | ELSEIF stimestmp+8(1) IS INITIAL. " date-only, default to 000000 time 137 | stimestmp+8(6) = '000000'. 138 | ELSEIF stimestmp(1) IS INITIAL. " time-only, default to current date 139 | stimestmp(8) = sy-datlo. 140 | ENDIF. 141 | 142 | rv_tstm = stimestmp. 143 | 144 | IF offset_sign IS NOT INITIAL. 145 | seconds = offset_hours * 3600 + offset_minutes * 60. 146 | IF offset_sign EQ '+'. 147 | rv_tstm = cl_abap_tstmp=>subtractsecs( tstmp = rv_tstm secs = seconds ). 148 | ELSE. 149 | rv_tstm = cl_abap_tstmp=>add( tstmp = rv_tstm secs = seconds ). 150 | ENDIF. 151 | ENDIF. 152 | 153 | ENDMETHOD. 154 | 155 | METHOD read_edm_datetime. 156 | 157 | CONSTANTS: lc_epochs TYPE c LENGTH 15 VALUE '19700101000000.'. 158 | 159 | DATA: ticks TYPE c LENGTH 21, 160 | offset_sign TYPE c, 161 | offset TYPE c LENGTH 4, 162 | pticks TYPE p, 163 | pseconds TYPE p, 164 | psubsec TYPE p, 165 | stimestmp TYPE string. 166 | 167 | FIND FIRST OCCURRENCE OF REGEX so_regex_edm_date_time IN in SUBMATCHES ticks offset_sign offset. 168 | CHECK sy-subrc IS INITIAL. 169 | 170 | pticks = ticks. 171 | pseconds = pticks / 1000. " in seconds 172 | psubsec = pticks MOD 1000. " in subsec 173 | 174 | stimestmp = psubsec. 175 | CONCATENATE lc_epochs stimestmp INTO stimestmp. 176 | rv_tstm = stimestmp. 177 | 178 | rv_tstm = cl_abap_tstmp=>add( tstmp = rv_tstm secs = pseconds ). 179 | 180 | IF offset_sign IS NOT INITIAL. 181 | pticks = offset * 60. "offset is in minutes 182 | IF offset_sign EQ '+'. 183 | rv_tstm = cl_abap_tstmp=>subtractsecs( tstmp = rv_tstm secs = pticks ). 184 | ELSE. 185 | rv_tstm = cl_abap_tstmp=>add( tstmp = rv_tstm secs = pticks ). 186 | ENDIF. 187 | ENDIF. 188 | 189 | ENDMETHOD. 190 | 191 | " Creates MD5 hash from string 192 | METHOD to_md5. 193 | DATA: lv_md5_key TYPE hash160. 194 | 195 | IF iv_value IS NOT INITIAL. 196 | CALL FUNCTION 'CALCULATE_HASH_FOR_CHAR' 197 | EXPORTING 198 | alg = 'MD5' 199 | data = iv_value 200 | IMPORTING 201 | hash = lv_md5_key 202 | EXCEPTIONS 203 | OTHERS = 4. 204 | 205 | IF sy-subrc EQ 0. 206 | rv_result = lv_md5_key. 207 | ENDIF. 208 | ENDIF. 209 | ENDMETHOD. "to_md5 210 | 211 | METHOD _escape. 212 | out = escape( val = in format = cl_abap_format=>e_json_string ). 213 | ENDMETHOD. "_escape 214 | 215 | ENDCLASS. "lcl_util IMPLEMENTATION 216 | 217 | *----------------------------------------------------------------------* 218 | * CLASS lcl_test DEFINITION 219 | *----------------------------------------------------------------------* 220 | * 221 | *----------------------------------------------------------------------* 222 | CLASS lcl_test DEFINITION FINAL FRIENDS z_ui2_json. 223 | 224 | PUBLIC SECTION. 225 | DATA: id TYPE i. 226 | DATA: children TYPE STANDARD TABLE OF REF TO lcl_test. 227 | 228 | METHODS: constructor. 229 | 230 | PROTECTED SECTION. "#EC SEC_PROTEC 231 | DATA: prot TYPE i. "#EC NEEDED 232 | 233 | PRIVATE SECTION. 234 | DATA: priv TYPE i. "#EC NEEDED 235 | 236 | ENDCLASS. "lcl_test DEFINITION 237 | 238 | *----------------------------------------------------------------------* 239 | * CLASS lcl_test IMPLEMENTATION 240 | *----------------------------------------------------------------------* 241 | * 242 | *----------------------------------------------------------------------* 243 | CLASS lcl_test IMPLEMENTATION. 244 | 245 | METHOD constructor. 246 | priv = 1. 247 | prot = 2. 248 | ENDMETHOD. "constructor 249 | 250 | ENDCLASS. "lcl_test IMPLEMENTATION 251 | 252 | *----------------------------------------------------------------------* 253 | * CLASS lc_json_custom DEFINITION 254 | *----------------------------------------------------------------------* 255 | * 256 | *----------------------------------------------------------------------* 257 | CLASS lc_json_custom DEFINITION FINAL INHERITING FROM z_ui2_json. 258 | PUBLIC SECTION. 259 | CLASS-METHODS: 260 | serialize_ex IMPORTING data TYPE data 261 | compress TYPE bool DEFAULT c_bool-false 262 | pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none 263 | RETURNING VALUE(r_json) TYPE json, 264 | deserialize_ex IMPORTING json TYPE json OPTIONAL 265 | pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none 266 | CHANGING data TYPE data. 267 | 268 | PROTECTED SECTION. 269 | METHODS: 270 | is_compressable REDEFINITION, 271 | pretty_name_ex REDEFINITION, 272 | dump_type REDEFINITION. 273 | ENDCLASS. "lc_json_custom DEFINITION 274 | 275 | *----------------------------------------------------------------------* 276 | * CLASS lc_json_custom IMPLEMENTATION 277 | *----------------------------------------------------------------------* 278 | * 279 | *----------------------------------------------------------------------* 280 | CLASS lc_json_custom IMPLEMENTATION. 281 | 282 | METHOD serialize_ex. 283 | DATA: lo_json TYPE REF TO lc_json_custom. 284 | CREATE OBJECT lo_json 285 | EXPORTING 286 | compress = compress 287 | pretty_name = pretty_name 288 | assoc_arrays = abap_true 289 | assoc_arrays_opt = abap_true 290 | expand_includes = abap_true 291 | numc_as_string = abap_true 292 | bool_types = `\TYPE-POOL=ABAP\TYPE=ABAP_BOOL\TYPE=BOOLEAN\TYPE=/UI2/BOOLEAN` 293 | ts_as_iso8601 = abap_true. 294 | r_json = lo_json->serialize_int( data = data ). 295 | ENDMETHOD. "serialize_ex 296 | 297 | METHOD deserialize_ex. 298 | DATA: lo_json TYPE REF TO lc_json_custom. 299 | IF json IS NOT INITIAL. 300 | CREATE OBJECT lo_json 301 | EXPORTING 302 | pretty_name = pretty_name 303 | assoc_arrays = abap_true 304 | assoc_arrays_opt = abap_true. 305 | TRY . 306 | lo_json->deserialize_int( EXPORTING json = json CHANGING data = data ). 307 | CATCH cx_sy_move_cast_error. "#EC NO_HANDLER 308 | ENDTRY. 309 | ENDIF. 310 | ENDMETHOD. "deserialize_ex 311 | 312 | METHOD is_compressable. 313 | IF type_descr->absolute_name EQ `\TYPE=STRING` OR name EQ `INITIAL`. 314 | rv_compress = abap_false. 315 | ELSE. 316 | rv_compress = abap_true. 317 | ENDIF. 318 | ENDMETHOD. "is_compressable 319 | 320 | METHOD pretty_name_ex. 321 | out = super->pretty_name_ex( in ). 322 | CONCATENATE out 'Xxx' INTO out. 323 | ENDMETHOD. "pretty_name 324 | 325 | METHOD dump_type. 326 | 327 | DATA: is_ddic TYPE abap_bool, 328 | ddic_field TYPE dfies. 329 | 330 | is_ddic = type_descr->is_ddic_type( ). 331 | IF is_ddic EQ abap_true. 332 | ddic_field = type_descr->get_ddic_field( ). 333 | IF mv_ts_as_iso8601 EQ c_bool-true AND ddic_field-domname EQ `TZNTSTMPL`. 334 | r_json = data. 335 | CONCATENATE `"` r_json(4) `-` r_json+4(2) `-` r_json+6(2) `T` r_json+8(2) `:` r_json+10(2) `:` r_json+12(2) `.` r_json+15(7) `Z"` INTO r_json. 336 | RETURN. 337 | ENDIF. 338 | ENDIF. 339 | IF mv_ts_as_iso8601 EQ c_bool-true AND type_descr->absolute_name EQ `\TYPE=LCM_CHANGED_ON`. 340 | r_json = data. 341 | CONCATENATE `"` r_json(4) `-` r_json+4(2) `-` r_json+6(2) `T` r_json+8(2) `:` r_json+10(2) `:` r_json+12(2) `.` r_json+15(7) `Z"` INTO r_json. 342 | RETURN. 343 | ENDIF. 344 | 345 | r_json = super->dump_type( data = data type_descr = type_descr convexit = convexit ). 346 | 347 | ENDMETHOD. "dump_type 348 | 349 | ENDCLASS. "lc_json_custom 350 | -------------------------------------------------------------------------------- /src/z_ui2_json.clas.macros.abap: -------------------------------------------------------------------------------- 1 | *"* use this source file for any macro definitions you need 2 | *"* in the implementation part of the class 3 | 4 | DEFINE escape_json. 5 | " => 7.31 6 | * &2 = escape( val = &1 format = cl_abap_format=>e_json_string ). 7 | 8 | lcl_util=>_escape( EXPORTING in = &1 IMPORTING out = &2 ). 9 | 10 | * <= 7.31 (not full scope of escaping is supported) 11 | * &2 = &1. 12 | * 13 | ** replace all occurrences of regex `[\\"]` in &1 with `\\$0`. <-- this is slower than 2 plain replaces 14 | * REPLACE ALL OCCURRENCES OF '\' IN &2 WITH '\\'. 15 | * REPLACE ALL OCCURRENCES OF '"' IN &2 WITH '\"'. 16 | * 17 | * REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf IN &2 WITH '\r\n'. 18 | * REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>newline IN &2 WITH '\n'. 19 | * REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>horizontal_tab IN &2 WITH '\t'. 20 | END-OF-DEFINITION. 21 | 22 | DEFINE is_compressable. 23 | IF mv_compress EQ abap_false. 24 | &3 = abap_false. 25 | ELSEIF mv_extended IS INITIAL. 26 | &3 = abap_true. 27 | ELSE. 28 | &3 = is_compressable( type_descr = &1 name = &2 ). 29 | ENDIF. 30 | END-OF-DEFINITION. 31 | 32 | DEFINE dump_type. 33 | IF mv_extended IS INITIAL. 34 | dump_type_int &1 &3 &4 &5. 35 | ELSE. 36 | &4 = dump_type( data = &1 type_descr = &2 typekind = &3 convexit = &5 ). 37 | ENDIF. 38 | END-OF-DEFINITION. 39 | 40 | DEFINE xstring_to_string_int. 41 | IF mv_hex_as_base64 IS INITIAL. 42 | MOVE &1 TO &2. 43 | ELSE. 44 | &2 = xstring_to_string( &1 ). 45 | ENDIF. 46 | END-OF-DEFINITION. 47 | 48 | DEFINE string_to_xstring_int. 49 | IF mv_hex_as_base64 IS INITIAL. 50 | MOVE &1 TO &2. 51 | ELSE. 52 | string_to_xstring( EXPORTING in = &1 CHANGING out = &2 ). 53 | ENDIF. 54 | END-OF-DEFINITION. 55 | 56 | DEFINE format_list_output. 57 | IF mv_format_output EQ abap_true AND &2 IS NOT INITIAL. 58 | CONCATENATE ',' lv_indent INTO lv_lb. 59 | CONCATENATE LINES OF &2 INTO &4 SEPARATED BY lv_lb. 60 | CONCATENATE &1 lv_indent &4 indent &3 INTO &4. 61 | ELSE. 62 | CONCATENATE LINES OF &2 INTO &4 SEPARATED BY ','. 63 | CONCATENATE &1 &4 &3 INTO &4. 64 | ENDIF. 65 | END-OF-DEFINITION. " format_list_output 66 | 67 | DEFINE dump_type_int. 68 | 69 | CASE &2. 70 | WHEN e_typekind-convexit. 71 | IF &1 IS INITIAL. 72 | &3 = `""`. 73 | ELSE. 74 | TRY. 75 | CALL FUNCTION &4 76 | EXPORTING 77 | input = &1 78 | IMPORTING 79 | output = &3 80 | EXCEPTIONS 81 | OTHERS = 1. 82 | IF sy-subrc IS INITIAL. 83 | CONCATENATE '"' &3 '"' INTO &3. 84 | ENDIF. 85 | CATCH cx_root ##CATCH_ALL ##NO_HANDLER. 86 | ENDTRY. 87 | ENDIF. 88 | WHEN e_typekind-utclong. 89 | IF &1 IS INITIAL. 90 | &3 = mv_initial_ts. 91 | ELSE. 92 | DATA: utcl TYPE c LENGTH 27. 93 | utcl = &1. 94 | CONCATENATE '"' utcl(10) 'T' utcl+11(16) 'Z"' INTO &3. 95 | ENDIF. 96 | WHEN e_typekind-ts_iso8601. 97 | IF &1 IS INITIAL. 98 | &3 = mv_initial_ts. 99 | ELSE. 100 | DATA: ts TYPE c LENGTH 14. 101 | ts = &1. 102 | CONCATENATE '"' ts(4) '-' ts+4(2) '-' ts+6(2) 'T' ts+8(2) ':' ts+10(2) ':' ts+12(2) 'Z"' INTO &3. 103 | ENDIF. 104 | WHEN e_typekind-tsl_iso8601. 105 | IF &1 IS INITIAL. 106 | &3 = mv_initial_ts. 107 | ELSE. 108 | DATA: tsl TYPE c LENGTH 22. 109 | tsl = &1. 110 | CONCATENATE '"' tsl(4) '-' tsl+4(2) '-' tsl+6(2) 'T' tsl+8(2) ':' tsl+10(2) ':' tsl+12(2) '.' tsl+15(7) 'Z"' INTO &3. 111 | ENDIF. 112 | WHEN e_typekind-float. 113 | IF &1 IS INITIAL. 114 | &3 = `0`. 115 | ELSE. 116 | &3 = &1. 117 | ENDIF. 118 | WHEN e_typekind-int OR e_typekind-int1 OR e_typekind-int2 OR e_typekind-packed OR e_typekind-int8. 119 | IF &1 IS INITIAL. 120 | &3 = `0`. 121 | ELSE. 122 | &3 = &1. 123 | IF &1 LT 0. 124 | SHIFT &3 RIGHT CIRCULAR. 125 | ELSE. 126 | CONDENSE &3. 127 | ENDIF. 128 | ENDIF. 129 | WHEN e_typekind-numc_string. 130 | IF &1 IS INITIAL. 131 | &3 = `""`. 132 | ELSE. 133 | CONCATENATE '"' &1 '"' INTO &3. 134 | ENDIF. 135 | WHEN e_typekind-num. 136 | IF &1 IS INITIAL. 137 | &3 = `0`. 138 | ELSE. 139 | &3 = &1. 140 | SHIFT &3 LEFT DELETING LEADING '0'. 141 | ENDIF. 142 | WHEN e_typekind-json. 143 | &3 = &1. 144 | WHEN e_typekind-string OR e_typekind-csequence OR e_typekind-clike OR e_typekind-char. 145 | IF &1 IS INITIAL. 146 | &3 = `""`. 147 | ELSE. 148 | escape_json &1 &3. 149 | CONCATENATE '"' &3 '"' INTO &3. 150 | ENDIF. 151 | WHEN cl_abap_typedescr=>typekind_xstring OR cl_abap_typedescr=>typekind_hex. 152 | IF &1 IS INITIAL. 153 | &3 = `""`. 154 | ELSE. 155 | xstring_to_string_int &1 &3. 156 | CONCATENATE '"' &3 '"' INTO &3. 157 | ENDIF. 158 | WHEN e_typekind-bool OR e_typekind-tribool. 159 | IF &1 EQ c_bool-true. 160 | &3 = `true`. "#EC NOTEXT 161 | ELSEIF &1 IS INITIAL AND &2 EQ e_typekind-tribool. 162 | &3 = `null`. "#EC NOTEXT 163 | ELSE. 164 | &3 = `false`. "#EC NOTEXT 165 | ENDIF. 166 | WHEN e_typekind-date. 167 | IF &1 IS INITIAL. 168 | &3 = mv_initial_date. 169 | ELSE. 170 | CONCATENATE '"' &1(4) '-' &1+4(2) '-' &1+6(2) '"' INTO &3. 171 | ENDIF. 172 | WHEN e_typekind-time. 173 | IF &1 IS INITIAL. 174 | &3 = mv_initial_time. 175 | ELSE. 176 | CONCATENATE '"' &1(2) ':' &1+2(2) ':' &1+4(2) '"' INTO &3. 177 | ENDIF. 178 | WHEN e_typekind-enum. 179 | &3 = &1. 180 | CONCATENATE '"' &3 '"' INTO &3. 181 | WHEN OTHERS. 182 | IF &1 IS INITIAL. 183 | &3 = `null`. "#EC NOTEXT 184 | ELSE. 185 | &3 = &1. 186 | ENDIF. 187 | ENDCASE. 188 | 189 | END-OF-DEFINITION. 190 | 191 | DEFINE format_name. 192 | CASE &2. 193 | WHEN pretty_mode-camel_case. 194 | &3 = pretty_name( &1 ). 195 | WHEN pretty_mode-pascal_case. 196 | &3 = pretty_name( in = &1 pascal_case = c_bool-true ). 197 | WHEN pretty_mode-extended. 198 | &3 = pretty_name_ex( &1 ). 199 | WHEN pretty_mode-user_low_case. 200 | READ TABLE mt_name_mappings WITH TABLE KEY abap = &1 ASSIGNING . "#EC WARNOK 201 | IF sy-subrc IS INITIAL. 202 | &3 = -json. 203 | ELSE. 204 | &3 = &1. 205 | TRANSLATE &3 TO LOWER CASE. 206 | ENDIF. 207 | WHEN pretty_mode-user. 208 | READ TABLE mt_name_mappings WITH TABLE KEY abap = &1 ASSIGNING . "#EC WARNOK 209 | IF sy-subrc IS INITIAL. 210 | &3 = -json. 211 | ELSE. 212 | &3 = &1. 213 | ENDIF. 214 | WHEN pretty_mode-low_case. 215 | &3 = &1. 216 | TRANSLATE &3 TO LOWER CASE. 217 | WHEN OTHERS. 218 | &3 = &1. 219 | ENDCASE. 220 | END-OF-DEFINITION. 221 | 222 | DEFINE restore_reference. 223 | CREATE DATA data TYPE HANDLE &1. 224 | ASSIGN data->* TO . 225 | restore_type( EXPORTING json = json length = length type_descr = &1 CHANGING offset = offset data = ). 226 | END-OF-DEFINITION. 227 | 228 | DEFINE restore_reference_ex. " &1 - data, &2 - type_descr 229 | ref_descr ?= &2. 230 | data_descr ?= ref_descr->get_referenced_type( ). 231 | IF &1 IS INITIAL. 232 | IF data_descr->type_kind EQ data_descr->typekind_data. " REF TO DATA 233 | generate_int_ex( EXPORTING json = json length = length CHANGING offset = offset data = &1 ). 234 | RETURN. 235 | ELSE. 236 | CREATE DATA &1 TYPE HANDLE data_descr. 237 | ENDIF. 238 | ENDIF. 239 | data_ref ?= &1. 240 | ASSIGN data_ref->* TO . 241 | restore_type( EXPORTING json = json length = length type_descr = data_descr CHANGING data = offset = offset ). 242 | END-OF-DEFINITION. 243 | 244 | DEFINE throw_error. 245 | " RAISE EXCEPTION TYPE cx_sy_move_cast_error EXPORTING source_typename = |({ offset })|. 246 | RAISE EXCEPTION TYPE cx_sy_move_cast_error. 247 | END-OF-DEFINITION. 248 | 249 | DEFINE while_offset_cs. 250 | 251 | * >= 7.02 alternative 252 | &2 = find_any_not_of( val = json sub = &1 off = &2 ) ##NO_TEXT. 253 | IF &2 EQ -1. 254 | &2 = length. 255 | ENDIF. 256 | * >= 7.02 257 | 258 | * < 7.02 259 | * WHILE &2 < length. 260 | * FIND FIRST OCCURRENCE OF json+&2(1) IN &1. 261 | * IF sy-subrc IS NOT INITIAL. 262 | * EXIT. 263 | * ENDIF. 264 | * &2 = &2 + 1. 265 | * ENDWHILE. 266 | * < 7.02 267 | 268 | END-OF-DEFINITION. 269 | 270 | DEFINE while_offset_not_cs. 271 | 272 | * >= 7.02 alternative 273 | &3 = find_any_of( val = &2 sub = &1 off = &3 ). 274 | IF &3 EQ -1. 275 | &3 = length. 276 | ENDIF. 277 | * >= 7.02 278 | 279 | * < 7.02 280 | * WHILE &3 < length. 281 | * FIND FIRST OCCURRENCE OF &2+&3(1) IN &1. 282 | * IF sy-subrc IS INITIAL. 283 | * EXIT. 284 | * ENDIF. 285 | * &3 = &3 + 1. 286 | * ENDWHILE. 287 | * < 7.02 288 | 289 | END-OF-DEFINITION. 290 | 291 | DEFINE eat_white. 292 | while_offset_cs sv_white_space offset. 293 | IF offset GE length. 294 | throw_error. 295 | ENDIF. 296 | END-OF-DEFINITION. 297 | 298 | DEFINE eat_name. 299 | 300 | IF json+offset(1) NE '"'. 301 | throw_error. 302 | ENDIF. 303 | 304 | offset = mark = offset + 1. 305 | IF mark < length AND json+mark(1) EQ '"'. 306 | CLEAR &1. 307 | ELSE. 308 | FIND FIRST OCCURRENCE OF '"' IN SECTION OFFSET offset OF json MATCH OFFSET offset. 309 | IF sy-subrc IS NOT INITIAL. 310 | throw_error. 311 | ENDIF. 312 | pos = offset - 1. 313 | IF json+pos(1) EQ '\'. " escaped quotes inside -> fallback on advanced processing 314 | lcl_util=>read_string( EXPORTING json = json mark = mark CHANGING text = &1 offset = offset ). 315 | ELSE. 316 | match = offset - mark. 317 | &1 = json+mark(match). 318 | ENDIF. 319 | ENDIF. 320 | offset = offset + 1. 321 | 322 | END-OF-DEFINITION. 323 | 324 | DEFINE eat_number. 325 | mark = offset. 326 | while_offset_cs '0123456789+-eE.' offset. 327 | match = offset - mark. 328 | &1 = json+mark(match). 329 | END-OF-DEFINITION. 330 | 331 | DEFINE eat_bool. 332 | mark = offset. 333 | while_offset_cs 'aeflnrstu' offset. 334 | match = offset - mark. 335 | IF json+mark(match) EQ 'true' ##NO_TEXT. 336 | &1 = c_bool-true. 337 | ELSEIF json+mark(match) EQ 'false' ##NO_TEXT. 338 | IF type_descr IS BOUND AND mv_bool_3state CS type_descr->absolute_name. 339 | &1 = c_tribool-false. 340 | ELSE. 341 | &1 = c_bool-false. 342 | ENDIF. 343 | ELSEIF json+mark(match) EQ 'null' ##NO_TEXT. 344 | CLEAR &1. 345 | ENDIF. 346 | END-OF-DEFINITION. 347 | 348 | DEFINE eat_char. 349 | IF offset < length AND json+offset(1) EQ &1. 350 | offset = offset + 1. 351 | ELSE. 352 | throw_error. 353 | ENDIF. 354 | END-OF-DEFINITION. 355 | 356 | DEFINE create_regexp. 357 | " first try PCRE 358 | TRY. 359 | CALL METHOD cl_abap_regex=>('CREATE_PCRE') 360 | EXPORTING 361 | pattern = &2 362 | RECEIVING 363 | regex = &1. "#EC NOTEXT 364 | CATCH cx_sy_dyn_call_error. 365 | CREATE OBJECT &1 366 | EXPORTING 367 | pattern = &2 ##REGEX_POSIX ##NO_TEXT. "#EC NOTEXT 368 | ENDTRY. 369 | END-OF-DEFINITION. 370 | --------------------------------------------------------------------------------