├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── RELEASENOTES.md ├── header.txt ├── pom.xml └── src ├── main └── java │ └── net │ └── javacrumbs │ └── json2xml │ ├── ElementNameConverter.java │ ├── JsonSaxAdapter.java │ ├── JsonXmlHelper.java │ └── JsonXmlReader.java └── test └── java └── net └── javacrumbs └── json2xml ├── JsonSaxAdapterTest.java └── JsonTest.java /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "maven" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | java: [ '17', '21' ] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up JDK ${{ matrix.java }} 14 | uses: actions/setup-java@v4 15 | with: 16 | java-version: ${{ matrix.java }} 17 | distribution: 'zulu' 18 | - name: Cache Maven packages 19 | uses: actions/cache@v4.2.3 20 | with: 21 | path: ~/.m2 22 | key: m2-${{ hashFiles('**/pom.xml') }} 23 | restore-keys: m2 24 | - name: Build with Maven 25 | run: mvn test javadoc:javadoc 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.settings 2 | /.project 3 | /target 4 | *.iml 5 | /.idea 6 | -------------------------------------------------------------------------------- /LICENSE.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, 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Java Json to XML conversion [![Apache License 2](https://img.shields.io/badge/license-ASF2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt) [![CI](https://github.com/lukas-krecan/json2xml/actions/workflows/build.yml/badge.svg)](https://github.com/lukas-krecan/json2xml/actions/workflows/build.yml) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.javacrumbs/json-xml/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.javacrumbs/json-xml) 2 | ============================ 3 | 4 | Json2Xml project is a simple implementation of JSON to XML conversion. Under the hood it uses [Jackson](http://jackson.codehaus.org/) [pull parser](http://wiki.fasterxml.com/JacksonInFiveMinutes#Streaming_API_Example) and generates 5 | XML SAX events. This way the conversion has low memory consumption and is pretty fast. 6 | 7 | There is already [Jettison](http://jettison.codehaus.org/) project that has similar objective, unfortunately it can not handle JSON arrays properly. 8 | 9 | Json2Xml converts the following JSON 10 | 11 | 12 | 13 | 44 | 97 | 98 |
14 |
 15 |        {
 16 |        "document":{
 17 |           "a":1,
 18 |           "b":2,
 19 |           "c":{
 20 |              "d":"text"
 21 |           },
 22 |           "e":[1, 2, 3],
 23 |           "f":[[1, 2, 3], [4, 5,6]],
 24 |           "g":null,
 25 |           "h":[
 26 |              {
 27 |                 "i":true,
 28 |                 "j":false
 29 |              }
 30 |           ],
 31 |           "k":[
 32 |              [
 33 |                 {"l":1, "m":2}
 34 |              ],
 35 |              [
 36 |                 {"n":3, "o":4},
 37 |                 {"p":5, "q":6}
 38 |              ]
 39 |           ]
 40 |        }
 41 |     }
 42 |      
43 |
45 |
 46 |     <document>
 47 |        <a>1</a>
 48 |        <b>2</b>
 49 |        <c>
 50 |           <d>text</d>
 51 |        </c>
 52 |        <e>
 53 |           <e>1</e>
 54 |           <e>2</e>
 55 |           <e>3</e>
 56 |        </e>
 57 |        <f>
 58 |           <f>
 59 |              <f>1</f>
 60 |              <f>2</f>
 61 |              <f>3</f>
 62 |           </f>
 63 |           <f>
 64 |              <f>4</f>
 65 |              <f>5</f>
 66 |              <f>6</f>
 67 |           </f>
 68 |        </f>
 69 |        <g />
 70 |        <h>
 71 |           <h>
 72 |              <i>true</i>
 73 |              <j>false</j>
 74 |           </h>
 75 |        </h>
 76 |        <k>
 77 |           <k>
 78 |              <k>
 79 |                 <l>1</l>
 80 |                 <m>2</m>
 81 |              </k>
 82 |           </k>
 83 |           <k>
 84 |              <k>
 85 |                 <n>3</n>
 86 |                 <o>4</o>
 87 |              </k>
 88 |              <k>
 89 |                 <p>5</p>
 90 |                 <q>6</q>
 91 |              </k>
 92 |           </k>
 93 |        </k>
 94 |     </document>
 95 |      
96 |
99 | 100 | 101 | 102 | Usage 103 | ------------- 104 | 105 | If you have SAX content handler, you can use `net.javacrumbs.json2xml.JsonSaxAdapter` class directly. 106 | 107 | ContentHandler ch = ...; 108 | JsonSaxAdapter adapter = new JsonSaxAdapter(json, ch); 109 | adapter.parse(); 110 | 111 | Otherwise it's possible to use `net.javacrumbs.json2xml.JsonXmlReader` together with standard Java transformation. 112 | 113 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); 114 | InputSource source = new InputSource(...); 115 | Result result = ...; 116 | transformer.transform(new SAXSource(new JsonXmlReader(),source), result); 117 | 118 | For example 119 | 120 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); 121 | InputSource source = new InputSource(new StringReader(json)); 122 | DOMResult result = new DOMResult(); 123 | transformer.transform(new SAXSource(new JsonXmlReader(namespace, addTypeAttributes, artificialRootName), source), result); 124 | result.getNode(); 125 | 126 | Type attributes 127 | --------------- 128 | Since XML does not have any mechanism to reflect JSON type information, there is a new feature since json2xml version 1.2. You can switch on the `addTypeAttributes` flag using a 129 | constructor argument. Then you will get the type information in XML attributes like this: 130 | 131 | 132 | 133 | 1 134 | 2 135 | 136 | text 137 | 138 | 139 | 1 140 | 2 141 | 3 142 | 143 | 144 | 145 | 1 146 | 2 147 | 3 148 | 149 | 150 | 4 151 | 5 152 | 6 153 | 154 | 155 | 156 | 157 | 158 | true 159 | false 160 | 161 | 162 | 163 | 164 | 165 | 1 166 | 2 167 | 168 | 169 | 170 | 171 | 3 172 | 4 173 | 174 | 175 |

5

176 | 6 177 |
178 |
179 |
180 |
181 | 182 | Artificial root 183 | --------------- 184 | XML support only one root element but JSON documents may have multiple roots. To overcome this mismatch, 185 | you can specify `artificialRootName` which will generate artificial XML root with given name. 186 | `new JsonXmlReader(null, false, "artificialRoot")` will transform 187 | 188 | {"a":1, "b":2} 189 | 190 | to 191 | 192 | 193 | 1 194 | 2 195 | 196 | 197 | Converting XML DOM back to JSON 198 | --------------- 199 | If you have created an XML DOM node from JSON content with `addTypeAttributes` then you can convert it back to JSON using `net.javacrumbs.json2xml.JsonXmlHelper`. 200 | 201 | // convert JSON to DOM Node 202 | Node node = JsonXmlHelper.convertToDom(...); 203 | // do whatever on the DOM Node (eventually modify using XPATH it while respecting the type attributes) 204 | String json = JsonXmlHelper.convertToJson(node); 205 | 206 | This method has proven very useful to work on huge JSON document using XPATH and converting it back to JSON afterward. 207 | 208 | Name transformation 209 | ------------------- 210 | Other difference between JSON and XML are allowed names. In cases, when your JSON contains names not allowed as XML element names, 211 | an `ElementNameConverter` can be used. For example to convert '@' to '_' create the following reader 212 | 213 | new JsonXmlReader("", false, null, new ElementNameConverter() { 214 | public String convertName(String name) { 215 | return name.replaceAll("@","_"); 216 | } 217 | }) 218 | 219 | Compatibility notes: 220 | -------------------- 221 | Version 4.1 handles arrays differently than the previous version. The change is in handling of arrays of JSON objects. 222 | In older version it behaved erratically. Version 4 fixes the behavior, but is backwards-incompatible. 223 | 224 | Also note that version 2.0 and higher require Jackson 2.x If you need new features in Jackson 1.x, just file a ticket and 225 | I will backport the changes. 226 | 227 | *Please do not use version 4.0. It has broken handling of arrays. Since it has been in the wild for two days only, I do not assume 228 | anyone is using it.* 229 | 230 | Maven 231 | ----- 232 | To use with Maven, add this dependency 233 | 234 | 235 | net.javacrumbs 236 | json-xml 237 | 4.2 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /RELEASENOTES.md: -------------------------------------------------------------------------------- 1 | ## 4.1 2 | * Fixed array handling 3 | 4 | ## 4.0 DO NOT USE 5 | * Changed transformation of arrays 6 | -------------------------------------------------------------------------------- /header.txt: -------------------------------------------------------------------------------- 1 | Copyright 2009-2019 the original author or authors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | org.sonatype.oss 6 | oss-parent 7 | 9 8 | 9 | 10 | net.javacrumbs 11 | json-xml 12 | jar 13 | 4.3-SNAPSHOT 14 | json-xml 15 | Converts JSON to XML 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | com.fasterxml.jackson.core 24 | jackson-databind 25 | 2.18.3 26 | 27 | 28 | junit 29 | junit 30 | 4.13.2 31 | test 32 | 33 | 34 | xmlunit 35 | xmlunit 36 | 1.6 37 | test 38 | 39 | 40 | org.mockito 41 | mockito-core 42 | 5.17.0 43 | test 44 | 45 | 46 | net.javacrumbs.json-unit 47 | json-unit 48 | 4.1.0 49 | test 50 | 51 | 52 | 53 | 54 | 55 | maven-compiler-plugin 56 | 3.14.0 57 | 58 | ${target.java.version} 59 | ${target.java.version} 60 | 61 | 62 | 63 | maven-javadoc-plugin 64 | 3.11.2 65 | 66 | none 67 | 68 | 69 | 70 | com.mycila.maven-license-plugin 71 | maven-license-plugin 72 | 1.9.0 73 | 74 |
header.txt
75 | 76 | LICENSE.txt 77 | 78 |
79 |
80 |
81 |
82 | 83 | 84 | scm:git:git@github.com:lukas-krecan/json2xml.git 85 | scm:git:git@github.com:lukas-krecan/json2xml.git 86 | scm:git:git@github.com:lukas-krecan/json2xml.git 87 | 88 | 89 | 90 | 91 | lukas 92 | Lukas Krecan 93 | lukas@krecan.net 94 | 95 | 96 | 97 | 98 | 99 | The Apache Software License, Version 2.0 100 | LICENSE.txt 101 | 102 | 103 |
104 | -------------------------------------------------------------------------------- /src/main/java/net/javacrumbs/json2xml/ElementNameConverter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2009-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.javacrumbs.json2xml; 17 | 18 | /** 19 | * Callback for name conversion. Can be used to convert names, that would be invalid as XML element values. 20 | */ 21 | public interface ElementNameConverter { 22 | 23 | /** 24 | * Converts JSON object name to XML element name. 25 | * @param name 26 | * @return 27 | */ 28 | public String convertName(String name); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/javacrumbs/json2xml/JsonSaxAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2009-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.javacrumbs.json2xml; 17 | 18 | import com.fasterxml.jackson.core.JsonFactory; 19 | import com.fasterxml.jackson.core.JsonParseException; 20 | import com.fasterxml.jackson.core.JsonParser; 21 | import com.fasterxml.jackson.core.JsonToken; 22 | import org.xml.sax.Attributes; 23 | import org.xml.sax.ContentHandler; 24 | import org.xml.sax.Locator; 25 | import org.xml.sax.SAXException; 26 | import org.xml.sax.helpers.AttributesImpl; 27 | 28 | import java.io.IOException; 29 | 30 | import static com.fasterxml.jackson.core.JsonToken.END_ARRAY; 31 | import static com.fasterxml.jackson.core.JsonToken.END_OBJECT; 32 | import static com.fasterxml.jackson.core.JsonToken.FIELD_NAME; 33 | import static com.fasterxml.jackson.core.JsonToken.START_ARRAY; 34 | import static com.fasterxml.jackson.core.JsonToken.START_OBJECT; 35 | import static com.fasterxml.jackson.core.JsonToken.VALUE_NULL; 36 | 37 | /** 38 | * Converts JSON to SAX events. It can be used either directly 39 | *
 40 |  *  
 41 |  * 	ContentHandler ch = ...;
 42 |  * 	JsonSaxAdapter adapter = new JsonSaxAdapter(JsonSaxAdapterTest.JSON, ch);
 43 |  * 	adapter.parse();
 44 |  *  
 45 |  *  
46 | * 47 | * or using {@link JsonXmlReader} 48 | *
 49 |  *  
 50 |  * 	Transformer transformer = TransformerFactory.newInstance().newTransformer();
 51 |  * 	InputSource source = new InputSource(...);
 52 |  * 	Result result = ...;
 53 |  * 	transformer.transform(new SAXSource(new JsonXmlReader(),source), result);
 54 |  *  
 55 |  *  
56 | */ 57 | public class JsonSaxAdapter { 58 | 59 | private static final AttributesImpl EMPTY_ATTRIBUTES = new AttributesImpl(); 60 | 61 | private final JsonParser jsonParser; 62 | 63 | private final ContentHandler contentHandler; 64 | 65 | private final String namespaceUri; 66 | 67 | private final boolean addTypeAttributes; 68 | 69 | private final String artificialRootName; 70 | 71 | private final ElementNameConverter nameConverter; 72 | 73 | private static final JsonFactory JSON_FACTORY = new JsonFactory(); 74 | 75 | /** 76 | * Creates JsonSaxAdapter that coverts JSON to SAX events. 77 | * @param json JSON to parse 78 | * @param contentHandler target of SAX events 79 | */ 80 | public JsonSaxAdapter(final String json, final ContentHandler contentHandler) { 81 | this(parseJson(json), contentHandler); 82 | } 83 | 84 | 85 | /** 86 | * Creates JsonSaxAdapter that coverts JSON to SAX events. 87 | * @param jsonParser parsed JSON 88 | * @param contentHandler target of SAX events 89 | */ 90 | public JsonSaxAdapter(final JsonParser jsonParser, final ContentHandler contentHandler) { 91 | this(jsonParser, contentHandler, ""); 92 | } 93 | 94 | /** 95 | * Creates JsonSaxAdapter that coverts JSON to SAX events. 96 | * @param jsonParser parsed JSON 97 | * @param contentHandler target of SAX events 98 | * @param namespaceUri namespace of the generated XML 99 | */ 100 | public JsonSaxAdapter(final JsonParser jsonParser, final ContentHandler contentHandler, final String namespaceUri) { 101 | this(jsonParser, contentHandler, namespaceUri, false); 102 | } 103 | 104 | /** 105 | * Creates JsonSaxAdapter that coverts JSON to SAX events. 106 | * @param jsonParser parsed JSON 107 | * @param contentHandler target of SAX events 108 | * @param namespaceUri namespace of the generated XML 109 | * @param addTypeAttributes adds type information as attributes 110 | */ 111 | public JsonSaxAdapter(final JsonParser jsonParser, final ContentHandler contentHandler, final String namespaceUri, final boolean addTypeAttributes) { 112 | this(jsonParser, contentHandler, namespaceUri, addTypeAttributes, null); 113 | } 114 | 115 | /** 116 | * Creates JsonSaxAdapter that coverts JSON to SAX events. 117 | * @param jsonParser parsed JSON 118 | * @param contentHandler target of SAX events 119 | * @param namespaceUri namespace of the generated XML 120 | * @param addTypeAttributes adds type information as attributes 121 | * @param artificialRootName if set, an artificial root is generated so JSON documents with more roots can be handled. 122 | */ 123 | public JsonSaxAdapter(final JsonParser jsonParser, final ContentHandler contentHandler, final String namespaceUri, 124 | final boolean addTypeAttributes, final String artificialRootName) { 125 | this(jsonParser, contentHandler, namespaceUri, addTypeAttributes, artificialRootName, null); 126 | } 127 | 128 | /** 129 | * Creates JsonSaxAdapter that coverts JSON to SAX events. 130 | * @param jsonParser parsed JSON 131 | * @param contentHandler target of SAX events 132 | * @param namespaceUri namespace of the generated XML 133 | * @param addTypeAttributes adds type information as attributes 134 | * @param artificialRootName if set, an artificial root is generated so JSON documents with more roots can be handled. 135 | */ 136 | public JsonSaxAdapter(final JsonParser jsonParser, final ContentHandler contentHandler, final String namespaceUri, 137 | final boolean addTypeAttributes, final String artificialRootName, final ElementNameConverter nameConverter) { 138 | this.jsonParser = jsonParser; 139 | this.contentHandler = contentHandler; 140 | this.namespaceUri = namespaceUri; 141 | this.addTypeAttributes = addTypeAttributes; 142 | this.artificialRootName = artificialRootName; 143 | this.nameConverter = nameConverter; 144 | contentHandler.setDocumentLocator(new DocumentLocator()); 145 | } 146 | 147 | 148 | private static JsonParser parseJson(final String json) { 149 | try { 150 | return JSON_FACTORY.createParser(json); 151 | } catch (Exception e) { 152 | throw new ParserException("Parsing error", e); 153 | } 154 | } 155 | 156 | /** 157 | * Method parses JSON and emits SAX events. 158 | */ 159 | public void parse() throws ParserException { 160 | try { 161 | jsonParser.nextToken(); 162 | contentHandler.startDocument(); 163 | if (shouldAddArtificialRoot()) { 164 | startElement(artificialRootName); 165 | parseElement(artificialRootName, false); 166 | endElement(artificialRootName); 167 | } else if (START_OBJECT.equals(jsonParser.getCurrentToken())) { 168 | int elementsWritten = parseObject(); 169 | if (elementsWritten>1) { 170 | throw new ParserException("More than one root element. Can not generate legal XML. You can set artificialRootName to generate an artificial root."); 171 | } 172 | } else { 173 | throw new ParserException("Unsupported root element. Can not generate legal XML. You can set artificialRootName to generate an artificial root."); 174 | } 175 | contentHandler.endDocument(); 176 | } catch (Exception e) { 177 | throw new ParserException("Parsing error: " + e.getMessage(), e); 178 | } 179 | } 180 | 181 | private boolean shouldAddArtificialRoot() { 182 | return artificialRootName != null && artificialRootName.length() > 0; 183 | } 184 | 185 | /** 186 | * Parses generic object. 187 | * 188 | * @return number of elements written 189 | * @throws IOException 190 | * @throws JsonParseException 191 | * @throws Exception 192 | */ 193 | private int parseObject() throws Exception { 194 | int elementsWritten = 0; 195 | while (jsonParser.nextToken() != null && jsonParser.getCurrentToken() != END_OBJECT) { 196 | if (FIELD_NAME.equals(jsonParser.getCurrentToken())) { 197 | String elementName = convertName(jsonParser.getCurrentName()); 198 | //jump to element value 199 | jsonParser.nextToken(); 200 | startElement(elementName); 201 | parseElement(elementName, false); 202 | endElement(elementName); 203 | elementsWritten++; 204 | } else { 205 | throw new ParserException("Error when parsing. Expected field name got " + jsonParser.getCurrentToken()); 206 | } 207 | } 208 | return elementsWritten; 209 | } 210 | 211 | private String convertName(String name) { 212 | if (nameConverter != null) { 213 | return nameConverter.convertName(name); 214 | } else { 215 | return name; 216 | } 217 | } 218 | 219 | /** 220 | * Pares JSON element. 221 | * @param elementName 222 | * @param inArray if the element is in an array 223 | * @throws Exception 224 | */ 225 | private void parseElement(final String elementName, final boolean inArray) throws Exception { 226 | JsonToken currentToken = jsonParser.getCurrentToken(); 227 | if (inArray) { 228 | startElement(elementName); 229 | } 230 | if (START_OBJECT.equals(currentToken)) { 231 | parseObject(); 232 | } else if (START_ARRAY.equals(currentToken)) { 233 | parseArray(elementName); 234 | } else if (currentToken.isScalarValue()) { 235 | parseValue(); 236 | } 237 | if (inArray) { 238 | endElement(elementName); 239 | } 240 | } 241 | 242 | private void parseArray(final String elementName) throws Exception { 243 | while (jsonParser.nextToken() != END_ARRAY && jsonParser.getCurrentToken() != null) { 244 | parseElement(elementName, true); 245 | } 246 | } 247 | 248 | private void parseValue() throws Exception { 249 | if (VALUE_NULL != jsonParser.getCurrentToken()) { 250 | String text = jsonParser.getText(); 251 | contentHandler.characters(text.toCharArray(), 0, text.length()); 252 | } 253 | } 254 | 255 | 256 | private void startElement(final String elementName) throws SAXException { 257 | contentHandler.startElement(namespaceUri, elementName, elementName, getTypeAttributes()); 258 | } 259 | 260 | 261 | protected Attributes getTypeAttributes() { 262 | if (addTypeAttributes) { 263 | String currentTokenType = getCurrentTokenType(); 264 | if (currentTokenType != null) { 265 | AttributesImpl attributes = new AttributesImpl(); 266 | attributes.addAttribute("", "type", "type", "string", currentTokenType); 267 | return attributes; 268 | } else { 269 | return EMPTY_ATTRIBUTES; 270 | } 271 | } else { 272 | return EMPTY_ATTRIBUTES; 273 | } 274 | } 275 | 276 | 277 | protected String getCurrentTokenType() { 278 | switch (jsonParser.getCurrentToken()) { 279 | case VALUE_NUMBER_INT: 280 | return "int"; 281 | case VALUE_NUMBER_FLOAT: 282 | return "float"; 283 | case VALUE_FALSE: 284 | return "boolean"; 285 | case VALUE_TRUE: 286 | return "boolean"; 287 | case VALUE_STRING: 288 | return "string"; 289 | case VALUE_NULL: 290 | return "null"; 291 | case START_ARRAY: 292 | return "array"; 293 | default: 294 | return null; 295 | } 296 | } 297 | 298 | 299 | private void endElement(final String elementName) throws SAXException { 300 | contentHandler.endElement(namespaceUri, elementName, elementName); 301 | } 302 | 303 | public static class ParserException extends RuntimeException { 304 | private static final long serialVersionUID = 2194022343599245018L; 305 | 306 | public ParserException(final String message, final Throwable cause) { 307 | super(message, cause); 308 | } 309 | 310 | public ParserException(final String message) { 311 | super(message); 312 | } 313 | 314 | public ParserException(final Throwable cause) { 315 | super(cause); 316 | } 317 | 318 | } 319 | 320 | private class DocumentLocator implements Locator { 321 | 322 | public String getPublicId() { 323 | Object sourceRef = jsonParser.getCurrentLocation().getSourceRef(); 324 | if (sourceRef != null) { 325 | return sourceRef.toString(); 326 | } else { 327 | return ""; 328 | } 329 | } 330 | 331 | public String getSystemId() { 332 | return getPublicId(); 333 | } 334 | 335 | public int getLineNumber() { 336 | return jsonParser.getCurrentLocation() != null ? jsonParser.getCurrentLocation().getLineNr() : -1; 337 | } 338 | 339 | public int getColumnNumber() { 340 | return jsonParser.getCurrentLocation() != null ? jsonParser.getCurrentLocation().getColumnNr() : -1; 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/main/java/net/javacrumbs/json2xml/JsonXmlHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2009-2019 Gwenhaël Pasquiers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.javacrumbs.json2xml; 17 | 18 | import com.fasterxml.jackson.core.JsonFactory; 19 | import com.fasterxml.jackson.core.JsonGenerator; 20 | import java.io.IOException; 21 | import java.io.StringReader; 22 | import java.io.StringWriter; 23 | import java.math.BigDecimal; 24 | import javax.xml.transform.Transformer; 25 | import javax.xml.transform.TransformerConfigurationException; 26 | import javax.xml.transform.TransformerException; 27 | import javax.xml.transform.TransformerFactory; 28 | import javax.xml.transform.dom.DOMResult; 29 | import javax.xml.transform.sax.SAXSource; 30 | import org.w3c.dom.Document; 31 | import org.w3c.dom.Element; 32 | import org.w3c.dom.Node; 33 | 34 | import org.w3c.dom.NodeList; 35 | import org.xml.sax.InputSource; 36 | 37 | /** 38 | * Helper class that can be used for JSON -> XML and XML -> JSON transformation. 39 | * 40 | * It is mostly useful to go back to JSON after using JsonXmlReader and 41 | * eventually modifying the DOM using XPath. This class needs the type attribute 42 | * so it can only convert back XML created from JSON with addTypeAttributes 43 | * true. 44 | * 45 | *
 46 |  *  Node node = convertToDom(myJSONString, "", true, "root");
 47 |  *  // ...
 48 |  *  String json = convertToJson(node);
 49 |  * 
50 | */ 51 | public class JsonXmlHelper { 52 | 53 | static public enum TYPE { 54 | STRING, INT, FLOAT, BOOLEAN, NULL, ARRAY, OBJECT 55 | } 56 | 57 | /** 58 | * Helper method to convert JSON string to XML DOM 59 | * 60 | * @param json String containing the json document 61 | * @param namespace Namespace that will contain the generated dom nodes 62 | * @param addTypeAttributes Set to true to generate type attributes 63 | * @param artificialRootName Name of the artificial root element node 64 | * @return Document DOM node. 65 | * @throws javax.xml.transform.TransformerConfigurationException 66 | */ 67 | public static Node convertToDom(final String json, final String namespace, final boolean addTypeAttributes, final String artificialRootName) throws TransformerConfigurationException, TransformerException { 68 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); 69 | InputSource source = new InputSource(new StringReader(json)); 70 | DOMResult result = new DOMResult(); 71 | transformer.transform(new SAXSource(new JsonXmlReader(namespace, addTypeAttributes, artificialRootName), source), result); 72 | return result.getNode(); 73 | } 74 | 75 | /** 76 | * Simpler helper method to convert DOM node back to JSON. The node MUST 77 | * have the "type" attributes (generated with addTypeAttributes flag set as 78 | * true). 79 | * 80 | * @param node The DOM Node 81 | * @return The JSON string 82 | * @throws IOException 83 | */ 84 | public static String convertToJson(Node node) throws IOException { 85 | try (StringWriter writer = new StringWriter(); JsonGenerator generator = new JsonFactory().createGenerator(writer)) { 86 | convertToJson(node, generator, name -> name); 87 | return writer.toString(); 88 | } 89 | } 90 | 91 | /** 92 | * More complete helper method to convert DOM node back to JSON.The node 93 | * MUST have the "type" attributes (generated with addTypeAttributes flag 94 | * set as true).This method allows to customize the JsonGenerator. 95 | * 96 | * @param node The DOM Node 97 | * @param generator A configured JsonGenerator 98 | * @param converter Converter to convert elements names from XML to JSON 99 | * @throws IOException 100 | */ 101 | public static void convertToJson(Node node, JsonGenerator generator, ElementNameConverter converter) throws IOException { 102 | Element element; 103 | if (node instanceof Document) { 104 | element = ((Document) node).getDocumentElement(); 105 | } else if (node instanceof Element) { 106 | element = (Element) node; 107 | } else { 108 | throw new IllegalArgumentException("Node must be either a Document or an Element"); 109 | } 110 | 111 | TYPE type = toTYPE(element.getAttribute("type")); 112 | switch (type) { 113 | case OBJECT: 114 | case ARRAY: 115 | convertElement(generator, element, true, converter); 116 | break; 117 | default: 118 | throw new RuntimeException("invalid root type [" + type + "]"); 119 | } 120 | generator.close(); 121 | } 122 | 123 | /** 124 | * Convert a DOM element to Json, with special handling for arrays since arrays don't exist in XML. 125 | * @param generator 126 | * @param element 127 | * @param isArrayItem 128 | * @throws IOException 129 | */ 130 | private static void convertElement(JsonGenerator generator, Element element, boolean isArrayItem, ElementNameConverter converter) throws IOException { 131 | TYPE type = toTYPE(element.getAttribute("type")); 132 | String name = element.getTagName(); 133 | 134 | if (!isArrayItem) { 135 | generator.writeFieldName(converter.convertName(name)); 136 | } 137 | 138 | switch (type) { 139 | case OBJECT: 140 | generator.writeStartObject(); 141 | convertChildren(generator, element, false, converter); 142 | generator.writeEndObject(); 143 | break; 144 | case ARRAY: 145 | generator.writeStartArray(); 146 | convertChildren(generator, element, true, converter); 147 | generator.writeEndArray(); 148 | break; 149 | case STRING: 150 | generator.writeString(element.getTextContent()); 151 | break; 152 | case INT: 153 | case FLOAT: 154 | generator.writeNumber(new BigDecimal(element.getTextContent())); 155 | break; 156 | case BOOLEAN: 157 | generator.writeBoolean(Boolean.parseBoolean(element.getTextContent())); 158 | break; 159 | case NULL: 160 | generator.writeNull(); 161 | break; 162 | } 163 | } 164 | 165 | /** 166 | * Method to recurse within children elements and convert them to JSON too. 167 | * @param generator 168 | * @param element 169 | * @param isArray 170 | * @throws IOException 171 | */ 172 | private static void convertChildren(JsonGenerator generator, Element element, boolean isArray, ElementNameConverter converter) throws IOException { 173 | NodeList list = element.getChildNodes(); 174 | int len = list.getLength(); 175 | for (int i = 0; i < len; i++) { 176 | Node node = list.item(i); 177 | if (node.getNodeType() == Node.ELEMENT_NODE) { 178 | convertElement(generator, (Element) node, isArray, converter); 179 | } 180 | } 181 | } 182 | 183 | /** 184 | * Convert type attribute value to TYPE enum (no attribute = OBJECT) 185 | * 186 | * @param type The type as a string 187 | * @return 188 | */ 189 | private static TYPE toTYPE(String type) { 190 | if (null == type || type.trim().isEmpty()) { 191 | return TYPE.OBJECT; 192 | } else { 193 | return TYPE.valueOf(type.toUpperCase()); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/net/javacrumbs/json2xml/JsonXmlReader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2009-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.javacrumbs.json2xml; 17 | 18 | import com.fasterxml.jackson.core.JsonFactory; 19 | import com.fasterxml.jackson.core.JsonParser; 20 | import org.xml.sax.ContentHandler; 21 | import org.xml.sax.DTDHandler; 22 | import org.xml.sax.EntityResolver; 23 | import org.xml.sax.ErrorHandler; 24 | import org.xml.sax.InputSource; 25 | import org.xml.sax.SAXException; 26 | import org.xml.sax.SAXNotRecognizedException; 27 | import org.xml.sax.SAXNotSupportedException; 28 | import org.xml.sax.XMLReader; 29 | 30 | import java.io.IOException; 31 | 32 | /** 33 | * Helper class that can be used for JSON -> XML transformation. 34 | *
 35 |  *	Transformer transformer = TransformerFactory.newInstance().newTransformer();
 36 |  *	InputSource source = new InputSource(...);
 37 |  *	Result result = ...;
 38 |  *	transformer.transform(new SAXSource(new JsonXmlReader(namespace),source), result);
 39 |  * 
40 | */ 41 | public class JsonXmlReader implements XMLReader { 42 | 43 | private ContentHandler contentHandler; 44 | private final String namespaceUri; 45 | private final boolean addTypeAttributes; 46 | private final String artificialRootName; 47 | private final ElementNameConverter elementNameConverter; 48 | 49 | 50 | /** 51 | * Creates JsonXmlReader 52 | */ 53 | public JsonXmlReader() { 54 | this(""); 55 | } 56 | 57 | /** 58 | * Creates JsonXmlReader 59 | * @param namespaceUri namespace uri of the resulting XML. 60 | */ 61 | public JsonXmlReader(String namespaceUri) { 62 | this(namespaceUri, false); 63 | } 64 | 65 | /** 66 | * Creates JsonXmlReader 67 | * @param namespaceUri namespace uri of the resulting XML. 68 | * @param addTypeAttributes if true adds attributes with type info 69 | */ 70 | public JsonXmlReader(String namespaceUri, boolean addTypeAttributes) { 71 | this(namespaceUri, addTypeAttributes, null); 72 | } 73 | 74 | /** 75 | * Creates JsonXmlReader 76 | * @param namespaceUri namespace uri of the resulting XML. 77 | * @param addTypeAttributes if true adds attributes with type info 78 | * @param artificialRootName if set, an artificial root is generated so JSON documents with more roots can be handled. 79 | */ 80 | public JsonXmlReader(String namespaceUri, boolean addTypeAttributes, String artificialRootName) { 81 | this(namespaceUri, addTypeAttributes, artificialRootName, null); 82 | } 83 | 84 | /** 85 | * Creates JsonXmlReader 86 | * @param namespaceUri namespace uri of the resulting XML. 87 | * @param addTypeAttributes if true adds attributes with type info 88 | * @param artificialRootName if set, an artificial root is generated so JSON documents with more roots can be handled. 89 | * @param elementNameConverter converter to convert JSON object names to valid XML element names 90 | */ 91 | public JsonXmlReader(String namespaceUri, boolean addTypeAttributes, String artificialRootName, ElementNameConverter elementNameConverter) { 92 | this.namespaceUri = namespaceUri; 93 | this.addTypeAttributes = addTypeAttributes; 94 | this.artificialRootName = artificialRootName; 95 | this.elementNameConverter = elementNameConverter; 96 | } 97 | 98 | 99 | public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException { 100 | throw new UnsupportedOperationException(); 101 | } 102 | 103 | public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { 104 | 105 | } 106 | 107 | public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { 108 | throw new UnsupportedOperationException(); 109 | } 110 | 111 | 112 | public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { 113 | //ignore 114 | } 115 | 116 | public void setEntityResolver(EntityResolver resolver) { 117 | //ignore 118 | } 119 | 120 | public EntityResolver getEntityResolver() { 121 | throw new UnsupportedOperationException(); 122 | } 123 | 124 | public void setDTDHandler(DTDHandler handler) { 125 | //ignore 126 | } 127 | 128 | public DTDHandler getDTDHandler() { 129 | throw new UnsupportedOperationException(); 130 | } 131 | 132 | public void setContentHandler(ContentHandler handler) { 133 | this.contentHandler = handler; 134 | } 135 | 136 | public ContentHandler getContentHandler() { 137 | return contentHandler; 138 | } 139 | 140 | public void setErrorHandler(ErrorHandler handler) { 141 | //ignore 142 | 143 | } 144 | 145 | public ErrorHandler getErrorHandler() { 146 | throw new UnsupportedOperationException(); 147 | } 148 | 149 | 150 | public void parse(InputSource input) throws IOException, SAXException { 151 | JsonParser jsonParser = new JsonFactory().createParser(input.getCharacterStream()); 152 | new JsonSaxAdapter(jsonParser, contentHandler, namespaceUri, addTypeAttributes, artificialRootName, elementNameConverter).parse(); 153 | } 154 | 155 | public void parse(String systemId) throws IOException, SAXException { 156 | throw new UnsupportedOperationException(); 157 | } 158 | 159 | public String getNamespaceUri() { 160 | return namespaceUri; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/test/java/net/javacrumbs/json2xml/JsonSaxAdapterTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2009-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.javacrumbs.json2xml; 17 | 18 | import net.javacrumbs.json2xml.JsonSaxAdapter.ParserException; 19 | import org.custommonkey.xmlunit.Diff; 20 | import org.custommonkey.xmlunit.XMLUnit; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.w3c.dom.Node; 24 | import org.xml.sax.ContentHandler; 25 | import org.xml.sax.InputSource; 26 | 27 | import javax.xml.transform.Result; 28 | import javax.xml.transform.Transformer; 29 | import javax.xml.transform.TransformerFactory; 30 | import javax.xml.transform.dom.DOMResult; 31 | import javax.xml.transform.sax.SAXSource; 32 | import javax.xml.transform.stream.StreamResult; 33 | import java.io.ByteArrayOutputStream; 34 | import java.io.StringReader; 35 | import static net.javacrumbs.json2xml.JsonXmlHelper.convertToJson; 36 | import static net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals; 37 | 38 | import static org.junit.Assert.assertTrue; 39 | import static org.mockito.Mockito.mock; 40 | 41 | 42 | public class JsonSaxAdapterTest { 43 | 44 | public static final String JSON = "{\"document\":{\"a\":1,\"b\":2,\"c\":{\"d\":\"text\"},\"e\":[1,2,3],\"f\":[[1,2,3],[4,5,6]], \"g\":null, " + 45 | "\"h\":[{\"i\":true,\"j\":false}],\"k\":[[{\"l\":1,\"m\":2}],[{\"n\":3,\"o\":4},{\"p\":5,\"q\":6}]]}}"; 46 | 47 | private static final String XML = "\n" + 48 | "\n" + 49 | " 1\n" + 50 | " 2\n" + 51 | " \n" + 52 | " text\n" + 53 | " \n" + 54 | " \n" + 55 | " 1\n" + 56 | " 2\n" + 57 | " 3\n" + 58 | " \n" + 59 | " \n" + 60 | " \n" + 61 | " 1\n" + 62 | " 2\n" + 63 | " 3\n" + 64 | " \n" + 65 | " \n" + 66 | " 4\n" + 67 | " 5\n" + 68 | " 6\n" + 69 | " \n" + 70 | " \n" + 71 | " \n" + 72 | " \n" + 73 | " \n" + 74 | " true\n" + 75 | " false\n" + 76 | " \n" + 77 | " \n" + 78 | " \n" + 79 | " \n" + 80 | " \n" + 81 | " 1\n" + 82 | " 2\n" + 83 | " \n" + 84 | " \n" + 85 | " \n" + 86 | " \n" + 87 | " 3\n" + 88 | " 4\n" + 89 | " \n" + 90 | " \n" + 91 | "

5

\n" + 92 | " 6\n" + 93 | "
\n" + 94 | "
\n" + 95 | "
\n" + 96 | "
\n"; 97 | 98 | private static final String XML_WITH_TYPES = "" + 99 | "\n" + 100 | "\n" + 101 | " 1\n" + 102 | " 2\n" + 103 | " \n" + 104 | " text\n" + 105 | " \n" + 106 | " \n" + 107 | " 1\n" + 108 | " 2\n" + 109 | " 3\n" + 110 | " \n" + 111 | " \n" + 112 | " \n" + 113 | " 1\n" + 114 | " 2\n" + 115 | " 3\n" + 116 | " \n" + 117 | " \n" + 118 | " 4\n" + 119 | " 5\n" + 120 | " 6\n" + 121 | " \n" + 122 | " \n" + 123 | " \n" + 124 | " \n" + 125 | " \n" + 126 | " true\n" + 127 | " false\n" + 128 | " \n" + 129 | " \n" + 130 | " \n" + 131 | " \n" + 132 | " \n" + 133 | " 1\n" + 134 | " 2\n" + 135 | " \n" + 136 | " \n" + 137 | " \n" + 138 | " \n" + 139 | " 3\n" + 140 | " 4\n" + 141 | " \n" + 142 | " \n" + 143 | "

5

\n" + 144 | " 6\n" + 145 | "
\n" + 146 | "
\n" + 147 | "
\n" + 148 | "
"; 149 | 150 | @Before 151 | public void ignoreWhitespace() { 152 | XMLUnit.setIgnoreWhitespace(true); 153 | } 154 | 155 | @Test 156 | public void testParse() throws Exception { 157 | String xml = convertToXml(JSON); 158 | Diff diff = XMLUnit.compareXML(XML, xml); 159 | assertTrue(diff.toString(), diff.similar()); 160 | } 161 | 162 | @Test 163 | public void testParseArray() throws Exception { 164 | String xml = convertToXml("[{\"name\":\"smith\"},{\"skill\":\"java\"}]", new JsonXmlReader(null, false, "elem")); 165 | Diff diff = XMLUnit.compareXML("smithjava", xml); 166 | assertTrue(diff.toString(), diff.similar()); 167 | } 168 | 169 | @Test 170 | public void testParseArrayOfObjects() throws Exception { 171 | String xml = convertToXml("{\"equipments\":[{\"type\":\"charger\",\"cost\":\"1$\"},{\"type\":\"battery\",\"cost\":\"2$\"}]}", new JsonXmlReader()); 172 | Diff diff = XMLUnit.compareXML("" + 173 | " \n" + 174 | " \n" + 175 | " charger\n" + 176 | " 1$\n" + 177 | " \n" + 178 | " \n" + 179 | " battery\n" + 180 | " 2$\n" + 181 | " \n" + 182 | " ", xml); 183 | assertTrue(diff.toString(), diff.similar()); 184 | } 185 | 186 | @Test 187 | public void testParseSimple() throws Exception { 188 | String xml = convertToXml("1", new JsonXmlReader(null, false, "elem")); 189 | Diff diff = XMLUnit.compareXML("1", xml); 190 | assertTrue(diff.toString(), diff.similar()); 191 | } 192 | 193 | @Test(expected = ParserException.class) 194 | public void testParseSimpleNoRoot() throws Exception { 195 | ContentHandler contentHandler = mock(ContentHandler.class); 196 | JsonSaxAdapter adapter = new JsonSaxAdapter("1", contentHandler); 197 | adapter.parse(); 198 | } 199 | 200 | @Test 201 | public void testParseNamespace() throws Exception { 202 | String xml = convertToXml(JSON, new JsonXmlReader("http://javacrumbs.net/test")); 203 | String xmlWithNamespace = XML.replace("", ""); 204 | Diff diff = XMLUnit.compareXML(xmlWithNamespace, xml); 205 | assertTrue(diff.toString(), diff.similar()); 206 | } 207 | 208 | @Test 209 | public void testParseNamespaceWithAttributes() throws Exception { 210 | String xml = convertToXml(JSON, new JsonXmlReader("http://javacrumbs.net/test", true)); 211 | Diff diff = XMLUnit.compareXML(XML_WITH_TYPES, xml); 212 | assertTrue(diff.toString(), diff.similar()); 213 | } 214 | 215 | @Test 216 | public void testParseMultipleRootsArtificialRoot() throws Exception { 217 | String xml = convertToXml("{\"a\":1, \"b\":2}", new JsonXmlReader(null, false, "artificialRoot")); 218 | Diff diff = XMLUnit.compareXML("12", xml); 219 | assertTrue(diff.toString(), diff.similar()); 220 | } 221 | 222 | @Test 223 | public void testParseMultipleRootsArtificialRootWithNamespace() throws Exception { 224 | String xml = convertToXml("{\"a\":1, \"b\":2}", new JsonXmlReader("http://javacrumbs.net/test", false, "artificialRoot")); 225 | Diff diff = XMLUnit.compareXML("12", xml); 226 | assertTrue(diff.toString(), diff.similar()); 227 | } 228 | 229 | @Test(expected = ParserException.class) 230 | public void testMultipleRoots() throws Exception { 231 | ContentHandler contentHandler = mock(ContentHandler.class); 232 | JsonSaxAdapter adapter = new JsonSaxAdapter("{\"a\":1, \"b\":2}", contentHandler); 233 | adapter.parse(); 234 | } 235 | 236 | @Test 237 | public void testParseInvalidName() throws Exception { 238 | String xml = convertToXml("{\"@bum\":1}", new JsonXmlReader("", false, null, new ElementNameConverter() { 239 | public String convertName(String name) { 240 | return name.replaceAll("@","_"); 241 | } 242 | })); 243 | Diff diff = XMLUnit.compareXML("<_bum>1", xml); 244 | assertTrue(diff.toString(), diff.similar()); 245 | } 246 | 247 | @Test 248 | public void testArrayOfObjects() throws Exception { 249 | String xml = convertToXml("{\"root\":[{\"a\":1}, {\"b\":2}]}"); 250 | Diff diff = XMLUnit.compareXML("12", xml); 251 | assertTrue(diff.toString(), diff.similar()); 252 | } 253 | 254 | @Test 255 | public void testArrayOfArraysOfObjects() throws Exception { 256 | String xml = convertToXml("{\"root\":[[{\"a\":1, \"e\":true}, {\"b\":2}],[{\"c\":3}, {\"d\":4}]]}"); 257 | Diff diff = XMLUnit.compareXML( 258 | "\n" + 259 | " \n" + 260 | " \n" + 261 | " 1\n" + 262 | " true\n" + 263 | " \n" + 264 | " \n" + 265 | " 2\n" + 266 | " \n" + 267 | " \n" + 268 | " \n" + 269 | " \n" + 270 | " 3\n" + 271 | " \n" + 272 | " \n" + 273 | " 4\n" + 274 | " \n" + 275 | " \n" + 276 | "", xml); 277 | assertTrue(diff.toString(), diff.similar()); 278 | } 279 | @Test 280 | public void testArrayOfArraysOfObjectsInRoot() throws Exception { 281 | String xml = convertToXml("[[{\"a\":1, \"e\":true}, {\"b\":2}],[{\"c\":3}, {\"d\":4}]]", new JsonXmlReader("", false, "root")); 282 | Diff diff = XMLUnit.compareXML( 283 | "\n" + 284 | " \n" + 285 | " \n" + 286 | " 1\n" + 287 | " true\n" + 288 | " \n" + 289 | " \n" + 290 | " 2\n" + 291 | " \n" + 292 | " \n" + 293 | " \n" + 294 | " \n" + 295 | " 3\n" + 296 | " \n" + 297 | " \n" + 298 | " 4\n" + 299 | " \n" + 300 | " \n" + 301 | "", xml); 302 | assertTrue(diff.toString(), diff.similar()); 303 | } 304 | 305 | 306 | @Test 307 | public void testArrayOfScalars() throws Exception { 308 | String xml = convertToXml("{\"a\":[1,2,3]}"); 309 | Diff diff = XMLUnit.compareXML("123", xml); 310 | assertTrue(diff.toString(), diff.similar()); 311 | } 312 | 313 | @Test 314 | public void testMapOfObjects() throws Exception { 315 | String xml = convertToXml("{\"root\":{\"a\":1, \"b\":2}}"); 316 | Diff diff = XMLUnit.compareXML("12", xml); 317 | assertTrue(diff.toString(), diff.similar()); 318 | } 319 | 320 | @Test 321 | public void testArrayOfComplexObjects() throws Exception { 322 | String xml = convertToXml("{\"root\":[{\"a\":{\"a1\":1}}, {\"b\":2}]}"); 323 | Diff diff = XMLUnit.compareXML("12", xml); 324 | assertTrue(diff.toString(), diff.similar()); 325 | } 326 | 327 | @Test 328 | public void testXmlEscaping() throws Exception { 329 | String xml = convertToXml("{\"root\":\"\"}"); 330 | Diff diff = XMLUnit.compareXML("<a>", xml); 331 | assertTrue(diff.toString(), diff.similar()); 332 | } 333 | 334 | @Test 335 | public void testBackAndForth() throws Exception { 336 | Node node = JsonXmlHelper.convertToDom(JSON, null, true, "root"); 337 | String convertedBackJSON = JsonXmlHelper.convertToJson(node); 338 | assertJsonEquals(JSON, convertedBackJSON); 339 | } 340 | 341 | public static String convertToXml(final String json) throws Exception { 342 | return convertToXml(json, new JsonXmlReader()); 343 | } 344 | 345 | public static String convertToXml(final String json, final JsonXmlReader reader) throws Exception { 346 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 347 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); 348 | InputSource source = new InputSource(new StringReader(json)); 349 | Result result = new StreamResult(out); 350 | transformer.transform(new SAXSource(reader, source), result); 351 | return new String(out.toByteArray()); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/test/java/net/javacrumbs/json2xml/JsonTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2009-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.javacrumbs.json2xml; 17 | 18 | import org.junit.Test; 19 | import org.xml.sax.InputSource; 20 | 21 | import javax.xml.transform.Transformer; 22 | import javax.xml.transform.TransformerException; 23 | import javax.xml.transform.TransformerFactory; 24 | import javax.xml.transform.dom.DOMResult; 25 | import javax.xml.transform.sax.SAXSource; 26 | import java.io.StringReader; 27 | 28 | public class JsonTest { 29 | 30 | @Test 31 | public void testMe() throws TransformerException { 32 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); 33 | 34 | String json = "[{\"studentName\" : \"Foo\", \"subjects2marks\":\"50\", \"subjects1name\":\"English\", \"subjects1marks\":\"40\", \"subjects2name\":\"History\", \"Age\":\"12\"},{\"studentName\":\"Bar\", \"subjects2marks\":\"50\", \"subjects1name\":\"English\", \"subjects1marks\":\"40\", \"subjects3marks\":\"40\", \"subjects2name\":\"History\", \"Age\":\"12\", \"subjects3name\":\"Science\"}, {\"studentName\":\"Baz\", \"Age\":\"12\"}]"; 35 | InputSource source = new InputSource(new StringReader(json)); 36 | 37 | DOMResult result = new DOMResult(); 38 | 39 | transformer.transform(new SAXSource(new JsonXmlReader("http://www.w3.org/1999/xhtml", true, "details"), source), result); 40 | 41 | System.out.println(result.getNode().getChildNodes().item(0).getChildNodes().item(0).getChildNodes().item(0).getChildNodes().item(0)); 42 | } 43 | 44 | } 45 | --------------------------------------------------------------------------------