├── test.hxml ├── .gitignore ├── tests ├── build │ ├── build_interp.hxml │ ├── build_flash.hxml │ ├── build_cpp.hxml │ ├── build_hl.hxml │ ├── build_js.hxml │ ├── build_lua.hxml │ ├── build_neko.hxml │ ├── build_cs.hxml │ ├── build_java.hxml │ ├── build_php.hxml │ ├── build_python.hxml │ ├── build_cppia.hxml │ ├── build_jvm.hxml │ ├── build_common.hxml │ └── build_all.hxml ├── UIntTest.hx ├── OtherEnum.hx ├── OtherAbstract.hx ├── FinalTest.hx ├── ArrayTest.hx ├── ListTest.hx ├── Main.hx ├── AliasTest.hx ├── InheritanceTest.hx ├── GetSetTest.hx ├── JsonComparator.hx ├── MapTest.hx ├── ObjectTest.hx ├── CustomTest.hx ├── StructureTest.hx ├── EnumTest.hx └── AbstractTest.hx ├── .vscode ├── settings.json └── extensions.json ├── patched_utest └── utest │ └── utils │ └── AsyncUtils.hx ├── haxelib.json ├── .github └── workflows │ └── main.yaml ├── LICENSE ├── src └── json2object │ ├── JsonWriter.hx │ ├── JsonParser.hx │ ├── utils │ ├── schema │ │ ├── ParsingType.hx │ │ ├── JsonSchemaType.hx │ │ ├── JsonType.hx │ │ ├── JsonTypeTools.hx │ │ └── DataBuilder.hx │ ├── JsonSchemaWriter.hx │ ├── special │ │ └── VSCodeSchemaWriter.hx │ └── TypeTools.hx │ ├── Position.hx │ ├── ErrorUtils.hx │ ├── TypeUtils.hx │ ├── Error.hx │ ├── PositionUtils.hx │ ├── writer │ ├── StringUtils.hx │ └── DataBuilder.hx │ └── reader │ ├── BaseParser.hx │ └── DataBuilder.hx └── README.md /test.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_interp.hxml 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dump/ 2 | testout/ 3 | .unittest 4 | -------------------------------------------------------------------------------- /tests/build/build_interp.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | --interp 3 | -------------------------------------------------------------------------------- /tests/build/build_flash.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -swf testout/test.swf 3 | -------------------------------------------------------------------------------- /tests/build/build_cpp.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -cpp testout/cpp/ 3 | -cmd ./testout/cpp/Main 4 | -------------------------------------------------------------------------------- /tests/build/build_hl.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -hl testout/test.hl 3 | -cmd hl ./testout/test.hl 4 | -------------------------------------------------------------------------------- /tests/build/build_js.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -js testout/test.js 3 | -cmd node testout/test.js 4 | -------------------------------------------------------------------------------- /tests/build/build_lua.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -lua testout/test.lua 3 | -cmd lua testout/test.lua 4 | -------------------------------------------------------------------------------- /tests/build/build_neko.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -neko testout/test.n 3 | -cmd neko testout/test.n 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "testExplorer.showCollapseButton": true, 3 | "testExplorer.showExpandButton": 1 4 | } 5 | -------------------------------------------------------------------------------- /tests/build/build_cs.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -cs testout/cs/ 3 | -cmd mono ./testout/cs/bin/Main.exe 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nadako.vshaxe", 4 | "vshaxe.haxe-test-adapter" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tests/build/build_java.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -java testout/java/ 3 | -cmd java -jar testout/java/Main.jar 4 | -------------------------------------------------------------------------------- /tests/build/build_php.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -php testout/php 3 | -D php7 4 | -cmd php testout/php/index.php 5 | -------------------------------------------------------------------------------- /tests/build/build_python.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -python testout/test.py 3 | -cmd python3 testout/test.py 4 | -------------------------------------------------------------------------------- /tests/build/build_cppia.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -cppia testout/test.cppia 3 | -cmd haxelib run hxcpp testout/test.cppia 4 | -------------------------------------------------------------------------------- /tests/build/build_jvm.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_common.hxml 2 | -java testout/jvm/ 3 | -D jvm 4 | -cmd java -jar testout/jvm/Main.jar 5 | -------------------------------------------------------------------------------- /tests/build/build_common.hxml: -------------------------------------------------------------------------------- 1 | -main tests.Main 2 | -lib hxjsonast 3 | -lib utest 4 | -cp src 5 | -cp patched_utest 6 | --macro include("json2object") 7 | -------------------------------------------------------------------------------- /patched_utest/utest/utils/AsyncUtils.hx: -------------------------------------------------------------------------------- 1 | package utest.utils; 2 | 3 | /** 4 | Shadow version to avoid issue with variable named async which is a keyword on python 3.5+ but not escaped in haxe 3. 5 | **/ 6 | class AsyncUtils { 7 | static public inline function orResolved(_async:Null):Async { 8 | return _async == null ? Async.getResolved() : _async; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json2object", 3 | "url" : "https://github.com/elnabo/json2object/", 4 | "license": "MIT", 5 | "tags": ["json", "cross", "macro"], 6 | "description": "Type safe Haxe/JSON (de)serializer", 7 | "version": "3.11.0", 8 | "releasenote": "Prevent compilation server cache issues", 9 | "contributors": ["elnabo", "_ibilon"], 10 | "classPath": "src", 11 | "dependencies": { 12 | "hxjsonast": "" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/build/build_all.hxml: -------------------------------------------------------------------------------- 1 | tests/build/build_cs.hxml 2 | --next 3 | 4 | tests/build/build_flash.hxml 5 | --next 6 | 7 | #tests/build/build_hl.hxml 8 | #--next 9 | 10 | # tests/build/build_java.hxml 11 | # --next 12 | 13 | tests/build/build_js.hxml 14 | --next 15 | 16 | # tests/build/build_jvm.hxml 17 | # --next 18 | 19 | #tests/build/build_lua.hxml 20 | #--next 21 | 22 | #tests/build/build_neko.hxml 23 | #--next 24 | 25 | #tests/build/build_php.hxml 26 | #--next 27 | 28 | tests/build/build_python.hxml 29 | --next 30 | 31 | #tests/build/build_cpp.hxml 32 | #--next 33 | 34 | #tests/build/build_cppia.hxml 35 | #--next 36 | 37 | # needs to be last 38 | tests/build/build_interp.hxml 39 | --next 40 | -------------------------------------------------------------------------------- /tests/UIntTest.hx: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import json2object.JsonParser; 4 | import json2object.JsonWriter; 5 | import utest.Assert; 6 | 7 | class UIntTest implements utest.ITest { 8 | public function new () {} 9 | 10 | public function test1 () { 11 | var parser = new JsonParser(); 12 | var writer = new JsonWriter(); 13 | var data = parser.fromJson('2147483648'); 14 | Assert.equals(0, parser.errors.length); 15 | Assert.same(data, parser.fromJson(writer.write(data))); 16 | } 17 | 18 | public function test2 () { 19 | var parser = new JsonParser(); 20 | var data = parser.fromJson('2147483648.54'); 21 | var orcale:UInt = 0; 22 | Assert.equals(1, parser.errors.length); 23 | Assert.same(orcale, data); 24 | } 25 | } -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | linux-build: 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest, macos-latest] 9 | haxe: [4.2.4, 3.4.7] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup haxe 14 | uses: krdlab/setup-haxe@v1 15 | with: 16 | haxe-version: ${{ matrix.haxe }} 17 | - name: Setup C# 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: '3.1.x' 21 | - name: Setup Python 3 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: '3.x' 25 | - name: Setup nodejs 26 | uses: actions/setup-node@v2 27 | with: 28 | node-version: '14' 29 | - run: npm install 30 | - run: | 31 | haxe -version 32 | haxelib install hxjsonast 33 | haxelib install utest; 34 | haxelib install hxjava 35 | haxelib install hxcs 36 | haxelib install hxnodejs 37 | haxelib list 38 | haxe tests/build/build_all.hxml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2018 Guillaume Desquesnes, Valentin Lemière 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/json2object/JsonWriter.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | package json2object; 23 | 24 | @:genericBuild(json2object.writer.DataBuilder.build()) 25 | class JsonWriter { 26 | } -------------------------------------------------------------------------------- /src/json2object/JsonParser.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | package json2object; 23 | 24 | @:genericBuild(json2object.reader.DataBuilder.build()) 25 | class JsonParser { 26 | } 27 | -------------------------------------------------------------------------------- /tests/OtherEnum.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | @:enum 26 | abstract TestEnum (String) 27 | { 28 | var VAL1 = "value1"; 29 | var VAL2 = "value2"; 30 | var VAL3 = "value3"; 31 | } 32 | -------------------------------------------------------------------------------- /src/json2object/utils/schema/ParsingType.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object.utils.schema; 24 | 25 | typedef ParsingType = { 26 | useEnumDescriptions : Bool, 27 | useMarkdown : Bool, 28 | useMarkdownLabel : Bool 29 | } -------------------------------------------------------------------------------- /tests/OtherAbstract.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | @:forward 26 | abstract OtherAbstract (OtherAbstractData) 27 | { 28 | } 29 | 30 | private class OtherAbstractData 31 | { 32 | public var hello : String; 33 | } 34 | -------------------------------------------------------------------------------- /src/json2object/Position.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object; 24 | 25 | /** 26 | * A position inside a JSON file. 27 | */ 28 | typedef Position = { 29 | file:String, 30 | lines:Array, 31 | min:Int, 32 | max:Int 33 | } 34 | 35 | /** 36 | * A line inside a JSON file. 37 | */ 38 | typedef Line = { 39 | number:Int, 40 | start:Int, 41 | end:Int 42 | } 43 | -------------------------------------------------------------------------------- /src/json2object/utils/JsonSchemaWriter.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | package json2object.utils; 23 | 24 | #if display 25 | class JsonSchemaWriter { 26 | public function new (_:String='') {} 27 | public var schema(get, never):String; 28 | function get_schema() {return null;} 29 | } 30 | #else 31 | @:genericBuild(json2object.utils.schema.DataBuilder.build()) 32 | class JsonSchemaWriter { } 33 | #end -------------------------------------------------------------------------------- /src/json2object/utils/special/VSCodeSchemaWriter.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | package json2object.utils.special; 23 | 24 | #if display 25 | class VSCodeSchemaWriter { 26 | public function new (_:String='') {} 27 | public var schema(get, never):String; 28 | function get_schema() {return null;} 29 | } 30 | #else 31 | @:genericBuild(json2object.utils.schema.DataBuilder.build()) 32 | class VSCodeSchemaWriter { } 33 | #end -------------------------------------------------------------------------------- /src/json2object/utils/schema/JsonSchemaType.hx: -------------------------------------------------------------------------------- 1 | package json2object.utils.schema; 2 | 3 | typedef JsonSchemaType = { 4 | @:optional 5 | var const: Null; 6 | @:optional @:alias('const') 7 | var const_bool : Bool; 8 | @:optional @:alias('const') 9 | var const_int : Int; 10 | @:optional @:alias('const') 11 | var const_float : Float; 12 | @:optional @:alias('default') @:noquoting 13 | var defaultValue : String; 14 | @:optional 15 | var description: String; 16 | @:optional 17 | var markdownDescription: String; 18 | @:optional 19 | var type: String; 20 | @:optional @:alias("$schema") 21 | var schema: String; 22 | @:optional @:alias("$ref") 23 | var ref: String; 24 | @:optional 25 | var required: Array; 26 | @:optional 27 | var properties: Map; 28 | @:optional 29 | var additionalProperties: Bool; 30 | @:optional @:alias("additionalProperties") 31 | var additionalProperties_obj: JsonSchemaType; 32 | @:optional 33 | var items: JsonSchemaType; 34 | @:optional 35 | var patternProperties: Map; 36 | @:optional 37 | var anyOf: Array; 38 | @:optional 39 | var definitions: Map; 40 | @:optional 41 | var markdownEnumDescriptions: Array; 42 | @:optional 43 | var enumDescriptions: Array; 44 | @:optional @:alias("enum") 45 | var enum_string: Array>; 46 | @:optional @:alias("enum") 47 | var enum_int: Array>; 48 | @:optional @:alias("enum") 49 | var enum_float: Array>; 50 | @:optional @:alias("enum") 51 | var enum_bool: Array>; 52 | } 53 | -------------------------------------------------------------------------------- /src/json2object/utils/schema/JsonType.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object.utils.schema; 24 | 25 | enum JsonType { 26 | JTNull; 27 | JTSimple(t:String); 28 | JTString(s:String); 29 | JTInt(i:Int); 30 | JTFloat(f:String); 31 | JTBool(b:Bool); 32 | JTObject(properties:Map, required:Array, defaults:Map); 33 | JTArray(type:JsonType); 34 | JTEnumValues(values:Array); 35 | JTMap(onlyInt:Bool, type:JsonType); 36 | JTRef(name:String); 37 | JTAnyOf(types:Array); 38 | JTWithDescr(type:JsonType, descr:String); 39 | } -------------------------------------------------------------------------------- /tests/FinalTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.Error; 26 | import json2object.JsonParser; 27 | import json2object.JsonWriter; 28 | import json2object.utils.JsonSchemaWriter; 29 | import utest.Assert; 30 | 31 | typedef Data = { 32 | final foo:String; 33 | final bar:{ 34 | final foobar:String; 35 | } 36 | } 37 | 38 | class FinalTest implements utest.ITest 39 | { 40 | public function new () {} 41 | 42 | public function test1 () 43 | { 44 | var parser = new JsonParser(); 45 | var writer = new JsonParser(); 46 | var schema = new JsonSchemaWriter(); 47 | 48 | var data = parser.fromJson('{}', "test.json"); 49 | Assert.equals(2, parser.errors.length); 50 | Assert.equals(null, data.foo); 51 | Assert.equals(null, data.bar); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/ArrayTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import json2object.utils.JsonSchemaWriter; 28 | import utest.Assert; 29 | 30 | class ArrayTest implements utest.ITest { 31 | public function new () {} 32 | 33 | public function test1 () { 34 | var parser = new JsonParser>(); 35 | var writer = new JsonWriter>(); 36 | var data = parser.fromJson('[0,1,4,3]', ""); 37 | var oracle = [0,1,4,3]; 38 | for (i in 0...data.length) { 39 | Assert.equals(oracle[i], data[i]); 40 | } 41 | Assert.equals(0, parser.errors.length); 42 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 43 | 44 | data = parser.fromJson('[0,1,4.4,3]', ""); 45 | Assert.equals(1, parser.errors.length); 46 | oracle = [0,1,3]; 47 | for (i in 0...data.length) { 48 | Assert.equals(oracle[i], data[i]); 49 | } 50 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 51 | } 52 | 53 | #if !lua 54 | public function test2 () { 55 | var schema = new JsonSchemaWriter>().schema; 56 | var oracle = '{"$$schema": "http://json-schema.org/draft-07/schema#","items": {"type": "integer"},"type": "array"}'; 57 | Assert.isTrue(JsonComparator.areSame(oracle, schema)); 58 | } 59 | #end 60 | } 61 | -------------------------------------------------------------------------------- /tests/ListTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import json2object.utils.JsonSchemaWriter; 28 | import utest.Assert; 29 | 30 | class ListTest implements utest.ITest { 31 | public function new () {} 32 | 33 | public function test1 () { 34 | var parser = new JsonParser>(); 35 | var writer = new JsonWriter>(); 36 | var data = parser.fromJson('[0,1,4,3]', ""); 37 | var oracle = [0,1,4,3]; 38 | 39 | var i = 0; 40 | for (d in data) { 41 | Assert.equals(oracle[i], d); 42 | i++; 43 | } 44 | Assert.equals(0, parser.errors.length); 45 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 46 | 47 | data = parser.fromJson('[0,1,4.4,3]', ""); 48 | Assert.equals(1, parser.errors.length); 49 | oracle = [0,1,3]; 50 | i = 0; 51 | for (d in data) { 52 | Assert.equals(oracle[i], d); 53 | i++; 54 | } 55 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 56 | } 57 | 58 | #if !lua 59 | public function test2 () { 60 | var schema = new JsonSchemaWriter>().schema; 61 | var oracle = '{"$$schema": "http://json-schema.org/draft-07/schema#","items": {"type": "integer"},"type": "array"}'; 62 | Assert.isTrue(JsonComparator.areSame(oracle, schema)); 63 | } 64 | #end 65 | } 66 | -------------------------------------------------------------------------------- /tests/Main.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | class Main 26 | { 27 | public static function main () 28 | { 29 | printTarget(); 30 | 31 | var r = new utest.Runner(); 32 | 33 | r.addCase(new AbstractTest()); 34 | r.addCase(new AliasTest()); 35 | r.addCase(new ArrayTest()); 36 | r.addCase(new CustomTest()); 37 | r.addCase(new EnumTest()); 38 | #if haxe4 39 | r.addCase(new FinalTest()); 40 | #end 41 | r.addCase(new GetSetTest()); 42 | r.addCase(new InheritanceTest()); 43 | r.addCase(new ListTest()); 44 | r.addCase(new MapTest()); 45 | r.addCase(new ObjectTest()); 46 | r.addCase(new StructureTest()); 47 | r.addCase(new UIntTest()); 48 | 49 | utest.ui.Report.create(r, NeverShowSuccessResults, AlwaysShowHeader); 50 | r.run(); 51 | } 52 | 53 | static function printTarget() 54 | { 55 | #if (cpp && !cppia) 56 | trace("cpp"); 57 | #elseif cppia 58 | trace("cppia"); 59 | #elseif cs 60 | trace("cs"); 61 | #elseif flash 62 | trace("flash"); 63 | #elseif hl 64 | trace("hl"); 65 | #elseif interp 66 | trace("interp"); 67 | #elseif java 68 | trace("java"); 69 | #elseif js 70 | trace("js"); 71 | #elseif lua 72 | trace("lua"); 73 | #elseif neko 74 | trace("neko"); 75 | #elseif php 76 | trace("php"); 77 | #elseif python 78 | trace("python"); 79 | #else 80 | trace("unknown"); 81 | #end 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/AliasTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import utest.Assert; 28 | 29 | typedef Aliased = { 30 | @:alias("public") var isPublic : Bool; 31 | } 32 | 33 | typedef MultiAliased = { 34 | @:alias("first") @:alias("public") var isPublic : Bool; 35 | } 36 | 37 | class AliasedClass { 38 | @:alias("public") public var isPublic : Bool; 39 | } 40 | 41 | class MultiAliasedClass { 42 | @:alias("first") @:alias("public") public var isPublic : Bool; 43 | } 44 | 45 | class AliasTest implements utest.ITest 46 | { 47 | public function new () {} 48 | 49 | public function test1 () 50 | { 51 | var parser = new JsonParser(); 52 | var writer = new JsonWriter(); 53 | var data = parser.fromJson('{ "public": true }', "test"); 54 | Assert.isTrue(data.isPublic); 55 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 56 | } 57 | 58 | public function test2 () 59 | { 60 | var parser = new JsonParser(); 61 | var writer = new JsonWriter(); 62 | var data = parser.fromJson('{ "public": true }', "test"); 63 | Assert.isTrue(data.isPublic); 64 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 65 | } 66 | 67 | public function test3 () 68 | { 69 | var parser = new JsonParser(); 70 | var writer = new JsonWriter(); 71 | var data = parser.fromJson('{ "public": true }', "test"); 72 | Assert.isTrue(data.isPublic); 73 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 74 | } 75 | 76 | public function test4 () 77 | { 78 | var parser = new JsonParser(); 79 | var writer = new JsonWriter(); 80 | var data = parser.fromJson('{ "public": true }', "test"); 81 | Assert.isTrue(data.isPublic); 82 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/json2object/ErrorUtils.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object; 24 | 25 | /** 26 | * Allow the print of error in the same format as the haxe compiler. 27 | */ 28 | class ErrorUtils { 29 | public static function convertError (e:Error) : String { 30 | var pos = switch (e) { 31 | case IncorrectType(_, _, pos) | IncorrectEnumValue(_, _, pos) | InvalidEnumConstructor(_, _, pos) | UninitializedVariable(_, pos) | UnknownVariable(_, pos) | ParserError(_, pos) | CustomFunctionException(_, pos): pos; 32 | } 33 | 34 | var header = ""; 35 | if (pos != null) { 36 | var file = (pos.file == '') ? 'line' : '${pos.file}:'; 37 | if (pos.lines.length == 1) { 38 | header = '${file}${pos.lines[0].number}: characters ${pos.lines[0].start}-${pos.lines[0].end} : '; 39 | } 40 | else if (pos.lines.length > 1) { 41 | header = '${file}${pos.lines[0].number}: lines ${pos.lines[0].number}-${pos.lines[pos.lines.length-1].number} : '; 42 | } 43 | } 44 | 45 | return switch (e) 46 | { 47 | case IncorrectType(variable, expected, _): 48 | header + 'Variable \'$variable\' should be of type \'$expected\''; 49 | 50 | case IncorrectEnumValue(variable, expected, _): 51 | header + 'Identifier \'$variable\' isn\'t part of \'$expected\''; 52 | 53 | case InvalidEnumConstructor(variable, expected, _): 54 | header + 'Enum argument \'$variable\' should be of type \'$expected\''; 55 | 56 | case UnknownVariable(variable, _): 57 | header + 'Variable \'$variable\' isn\'t part of the schema'; 58 | 59 | case UninitializedVariable(variable, _): 60 | header + 'Variable \'$variable\' should be in the json'; 61 | 62 | case ParserError(message, _): 63 | header + 'Parser error: $message'; 64 | 65 | case CustomFunctionException(e, _): 66 | header + 'Custom function exception: $e'; 67 | } 68 | } 69 | 70 | public static function convertErrorArray (e:Array) : String { 71 | return e.map(convertError).join("\n"); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/InheritanceTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import json2object.utils.JsonSchemaWriter; 28 | import utest.Assert; 29 | 30 | class Parent { 31 | public var a : Int; 32 | } 33 | 34 | class Child extends Parent { 35 | public var b : String; 36 | } 37 | 38 | class OtherChild extends Parent { 39 | public var b : Bool; 40 | } 41 | 42 | class InheritanceTest implements utest.ITest { 43 | public function new () {} 44 | 45 | public function test1 () { 46 | var parser = new JsonParser(); 47 | var writer = new JsonWriter(); 48 | var data = parser.fromJson('{"a": 7}', "test.json"); 49 | Assert.equals(0, parser.errors.length); 50 | Assert.equals(7, data.a); 51 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 52 | } 53 | 54 | public function test2 () { 55 | var parser = new JsonParser(); 56 | var writer = new JsonWriter(); 57 | var data = parser.fromJson('{"a": 7, "b": "hello"}', "test.json"); 58 | Assert.equals(0, parser.errors.length); 59 | Assert.equals(7, data.a); 60 | Assert.equals("hello", data.b); 61 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 62 | } 63 | 64 | public function test3 () { 65 | var parser = new JsonParser(); 66 | var writer = new JsonWriter(); 67 | var data = parser.fromJson('{"a": 7, "b": true}', "test.json"); 68 | Assert.equals(0, parser.errors.length); 69 | Assert.isTrue(data.b); 70 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 71 | } 72 | 73 | public function test4 () { 74 | var parser = new JsonParser(); 75 | var writer = new JsonWriter(); 76 | var data = parser.fromJson('{"a": 7, "b": true}', "test.json"); 77 | Assert.equals(2, parser.errors.length); 78 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 79 | } 80 | 81 | public function test5 () { 82 | var parser = new JsonParser(); 83 | var writer = new JsonWriter(); 84 | var data = parser.fromJson('{"a": 7, "b": "hello"}', "test.json"); 85 | Assert.equals(2, parser.errors.length); 86 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/json2object/TypeUtils.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object; 24 | 25 | #if !macro 26 | class TypeUtils 27 | { 28 | } 29 | #else 30 | import haxe.macro.Expr; 31 | import haxe.macro.Context; 32 | import haxe.macro.Type; 33 | 34 | using json2object.utils.TypeTools; 35 | 36 | class TypeUtils 37 | { 38 | static var exist = new Map(); 39 | 40 | static function filterOnlyVar (ff:ClassField) : Bool 41 | { 42 | return switch (ff.kind) 43 | { 44 | case FVar(_, _): 45 | true; 46 | 47 | default: 48 | false; 49 | } 50 | } 51 | 52 | static function convertClassField (ff:ClassField) : Field 53 | { 54 | return { 55 | name: ff.name, 56 | doc: ff.doc, 57 | access: [APublic], 58 | kind: FVar(ff.type.toComplexType(), null), 59 | pos: ff.pos, 60 | meta: ff.meta.get() 61 | }; 62 | } 63 | 64 | public static function copyType(t:ClassType) : ClassType 65 | { 66 | t.pack.pop(); 67 | var n = t.name + "__json2object_nonprivate_copy"; 68 | var m = t.pack.join("."); 69 | 70 | t = { 71 | module: m, 72 | name: n, 73 | isPrivate: false, 74 | #if (haxe_ver >= 4) isFinal: false, #end 75 | init: t.init, 76 | constructor: t.constructor, 77 | doc: t.doc, 78 | params: t.params, 79 | pos: t.pos, 80 | fields: t.fields, 81 | overrides: t.overrides, 82 | pack: t.pack, 83 | kind: t.kind, 84 | meta: t.meta, 85 | superClass: t.superClass, 86 | interfaces: t.interfaces, 87 | statics: t.statics, 88 | isExtern: t.isExtern, 89 | #if haxe4 90 | #if (haxe >= version("4.2.0-rc.1")) 91 | isAbstract: t.isAbstract, 92 | #end 93 | #end 94 | exclude: t.exclude, 95 | isInterface: t.isInterface 96 | } 97 | 98 | if (!exist.exists(t.name)) 99 | { 100 | var td : TypeDefinition = { 101 | #if haxe4 102 | doc: t.doc, 103 | #end 104 | pack: t.pack, 105 | name: t.name, 106 | pos: t.pos, 107 | meta: t.meta.get(), 108 | params: [for (p in t.params) { name: p.name }], 109 | isExtern: t.isExtern, 110 | kind: TDClass(), 111 | fields: [for (f in Lambda.filter(t.fields.get(), filterOnlyVar)) convertClassField(f)] 112 | } 113 | 114 | Context.defineType(td); 115 | 116 | exist.set(t.name, true); 117 | } 118 | 119 | return t; 120 | } 121 | } 122 | #end 123 | -------------------------------------------------------------------------------- /src/json2object/Error.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object; 24 | 25 | /** 26 | * Error raised during an initialization by JSON. 27 | */ 28 | enum Error { 29 | /** 30 | * Mismatch between instance type and json type. 31 | * 32 | * @param variable Variable affected. 33 | * @param expected Expected type. 34 | * @param pos Position in the JSON file. 35 | */ 36 | IncorrectType(variable:String, expected:String, pos:Position); 37 | 38 | /** 39 | * Incorrect enum value. 40 | * 41 | * @param variable Value affected. 42 | * @param expected Expected type. 43 | * @param pos Position in the JSON file. 44 | */ 45 | IncorrectEnumValue(value:String, expected:String, pos:Position); 46 | 47 | /** 48 | * Incorrect enum contructor. 49 | * 50 | * @param variable Value affected. 51 | * @param expected Expected type. 52 | * @param pos Position in the JSON file. 53 | */ 54 | InvalidEnumConstructor(value:String, expected:String, pos:Position); 55 | 56 | /** 57 | * Variable has not been initialized. 58 | * 59 | * @param variable Variable affected. 60 | * @param pos Position in the JSON file. 61 | */ 62 | UninitializedVariable(variable:String, pos:Position); 63 | 64 | /** 65 | * No instance variable corresponding to this JSON variable. 66 | * 67 | * @param variable Variable affected. 68 | * @param pos Position in the JSON file. 69 | */ 70 | UnknownVariable(variable:String, pos:Position); 71 | 72 | /** 73 | * Incorrect JSON file formating. 74 | * 75 | * @param message Message raised. 76 | * @param pos Position in the JSON file. 77 | */ 78 | ParserError(message:String, pos:Position); 79 | 80 | /** 81 | * Custom function (@:jcustomparse or @:jcustomwrite) exception. 82 | * 83 | * @param e Exception raised. 84 | * @param pos Position in the JSON file. 85 | **/ 86 | CustomFunctionException(e:Any, pos:Position); 87 | } 88 | 89 | #if haxe4 enum #else @:enum #end abstract ErrorType(Int) { 90 | var NONE = 0; 91 | var OBJECTTHROW = 1; 92 | var THROW = 2; 93 | } 94 | 95 | enum InternalError { 96 | AbstractNoJsonRepresentation(name:String); 97 | CannotGenerateSchema(name:String); 98 | HandleExpr; 99 | ParsingThrow; 100 | UnsupportedAbstractEnumType(name:String); 101 | UnsupportedEnumAbstractValue(name:String); 102 | UnsupportedMapKeyType(name:String); 103 | UnsupportedSchemaObjectType(name:String); 104 | UnsupportedSchemaType(type:String); 105 | } 106 | 107 | class CustomFunctionError { 108 | public #if haxe4 final #else var #end message:String; 109 | 110 | @:allow(json2object.reader.DataBuilder) 111 | @:allow(json2object.writer.DataBuilder) 112 | function new(message:String) { 113 | this.message = message; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/GetSetTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.Error; 26 | import json2object.JsonParser; 27 | import json2object.JsonWriter; 28 | import utest.Assert; 29 | 30 | class GetSetTest implements utest.ITest { 31 | 32 | var h : Int = 1; 33 | public var i(get,set) : Int; 34 | function get_i () : Int { 35 | return h * 2; 36 | } 37 | function set_i (v:Int) : Int { 38 | return h = v; 39 | } 40 | 41 | @:isVar var j (get, set) : Float; 42 | function get_j () : Float { 43 | return j * 50; 44 | } 45 | function set_j (j:Float) : Float { 46 | return this.j = j; 47 | } 48 | 49 | var k (get, set) : Int; 50 | function get_k () : Int { 51 | return Std.int(j); 52 | } 53 | function set_k (v:Int) : Int { 54 | j = v; 55 | return v; 56 | } 57 | 58 | var l (default, default) : Int = 1; 59 | var m (default, never) : Int = 2; 60 | var n (default, null) : Int = 3; 61 | var o (default, set) : Int; 62 | function set_o (v:Int) { 63 | return this.o = v * 2; 64 | } 65 | 66 | var q (null, default) : Int = 5; 67 | var s (null, null) : Int = 7; 68 | var t (get, null) : Int = 8; 69 | function get_t () :Int { return 1; } 70 | 71 | var u (null, never) : Int; 72 | var v (null, set) : Int; 73 | function set_v (v:Int) : Int { 74 | return this.v = v; 75 | } 76 | 77 | public function new () {} 78 | 79 | public function test1 () { 80 | var gst = new GetSetTest(); 81 | gst.j = Math.PI; 82 | gst.o = 2; 83 | Reflect.setField(gst, "u", 2); 84 | gst.v = 9; 85 | 86 | var json = '{"h":1,"j":3.14159265358979,"l":1,"m":2,"n":3,"o":4,"q":5,"s":7,"t":8,"u":2,"v":9}'; 87 | 88 | var parser = new JsonParser(); 89 | var writer = new JsonWriter(); 90 | var data = parser.fromJson(json, "test"); 91 | Assert.same(gst, data); 92 | Assert.equals(0, parser.errors.length); 93 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 94 | } 95 | 96 | public function test2 () { 97 | var gst = new GetSetTest(); 98 | gst.k = 4; 99 | gst.l = 3; 100 | Reflect.setField(gst, "m", 4); 101 | gst.n = 5; 102 | gst.o = 2; 103 | Reflect.setField(gst, "u", 23); 104 | gst.v = 9; 105 | 106 | var json = '{"j":4.0, "h":1, "k":3, "l":3, "m":4, "n":5, "o":4, "q":5, "s":7, "t":8, "u":23, "v":9}'; 107 | 108 | var parser = new JsonParser(); 109 | var writer = new JsonWriter(); 110 | var data = parser.fromJson(json, "test"); 111 | Assert.same(gst, data); 112 | Assert.same(UnknownVariable("k", {file:"test", lines:[{number:1, start:18, end:21}], min:18, max:21}), parser.errors[0]); 113 | Assert.equals(1, parser.errors.length); 114 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tests/JsonComparator.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | using StringTools; 26 | 27 | class JsonComparator { 28 | 29 | static function splitArray(arr:String) : Array { 30 | var result = []; 31 | arr = arr.substr(1); 32 | var length = arr.length; 33 | 34 | var inObjDepth = 0; 35 | var inArrayDepth = 0; 36 | 37 | var i = 0; 38 | while (i < length) { 39 | var next = i; 40 | while (next < length) { 41 | var c = arr.charAt(next); 42 | switch (c) { 43 | case '[': inArrayDepth++; 44 | case ']': inArrayDepth--; 45 | case '{': inObjDepth++; 46 | case '}': inObjDepth--; 47 | case ',' if (inObjDepth == 0 && inArrayDepth == 0): break; 48 | } 49 | next++; 50 | } 51 | result.push(arr.substring(i, next)); 52 | i = next + 1; 53 | } 54 | return result; 55 | } 56 | 57 | static function splitObject (obj:String) : Map { 58 | var result = new Map(); 59 | obj = obj.substr(1); 60 | var length = obj.length; 61 | var key = ''; 62 | 63 | var inObjDepth = 0; 64 | var inArrayDepth = 0; 65 | 66 | var valueStart = 0; 67 | 68 | var i = 0; 69 | while (i < length) { 70 | var next = obj.indexOf(':', i); 71 | key = obj.substr(i, next).trim(); 72 | 73 | next++; 74 | var valueStart = next; 75 | 76 | while (next < length) { 77 | var c = obj.charAt(next); 78 | switch (c) { 79 | case '[': inArrayDepth++; 80 | case ']': inArrayDepth--; 81 | case '{': inObjDepth++; 82 | case '}': inObjDepth--; 83 | case ',' if (inObjDepth == 0 && inArrayDepth == 0): break; 84 | } 85 | next++; 86 | } 87 | result.set(key, obj.substring(valueStart, next)); 88 | i = next + 1; 89 | } 90 | return result; 91 | } 92 | 93 | public static function areSame (a:String, b:String) : Bool { 94 | if (a == b) { 95 | return true; 96 | } 97 | 98 | var length = a.length; 99 | if (b.length != length) { 100 | return false; 101 | } 102 | 103 | var firstA = a.charAt(0); 104 | var firstB = b.charAt(0); 105 | 106 | if (firstA == '{' && firstB == '{') { 107 | var propsA = splitObject(a); 108 | var propsB = splitObject(a); 109 | 110 | for (key in propsA.keys()) { 111 | if (!propsB.exists(key)) { 112 | return false; 113 | } 114 | if (!areSame(propsA.get(key), propsB.get(key))) { 115 | return false; 116 | } 117 | } 118 | 119 | return true; 120 | } 121 | 122 | if (firstA == '[' && firstB == '[') { 123 | var valuesA = splitArray(a); 124 | var valuesB = splitArray(b); 125 | 126 | for (s1 in valuesA) { 127 | var found = false; 128 | for (s2 in valuesB) { 129 | if (areSame(s1, s2)) { 130 | found = true; 131 | break; 132 | } 133 | } 134 | if (!found) { 135 | return false; 136 | } 137 | } 138 | return true; 139 | } 140 | 141 | return false; 142 | } 143 | } -------------------------------------------------------------------------------- /src/json2object/PositionUtils.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016-2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object; 24 | 25 | /** 26 | * Transform a hxjsonast.Position into a Position, which provides information on lines. 27 | */ 28 | class PositionUtils { 29 | /** Store line information (number/start/end). */ 30 | var linesInfo = new Array(); 31 | 32 | public function new(content:String) { 33 | var s = 0; // Line start char 34 | var e = 0; // Line end char 35 | 36 | var i = 0; 37 | var lineCount = 0; 38 | while (i < content.length) { 39 | switch (content.charAt(i)) { 40 | case "\r": 41 | e = i; 42 | if (content.charAt(i+1) == "\n") { 43 | e++; 44 | } 45 | linesInfo.push({ number: lineCount, start: s, end: e }); 46 | lineCount++; 47 | i = e+1; 48 | s = i; 49 | case "\n": 50 | e = i; 51 | linesInfo.push({ number: lineCount, start: s, end: e }); 52 | lineCount++; 53 | i++; 54 | s = i; 55 | default: 56 | i++; 57 | } 58 | } 59 | linesInfo.push({number: lineCount, start: s, end: i }); 60 | } 61 | 62 | /** 63 | * Convert a hxjsonast.Position into a Position who contains information on lines. 64 | */ 65 | public function convertPosition(position:hxjsonast.Position):Position { 66 | var file = position.file; 67 | var min = position.min; 68 | var max = position.max; 69 | 70 | var pos = {file: file, min: min+1, max: max+1, lines:[]}; 71 | var lastLine = linesInfo.length - 1; 72 | 73 | var bounds = {min:0, max:lastLine}; 74 | if (min > linesInfo[0].end) { 75 | while (bounds.max > bounds.min) { 76 | var i = Std.int((bounds.min + bounds.max) / 2); 77 | var line = linesInfo[i]; 78 | if (line.start == min) { 79 | bounds.min = i; 80 | bounds.max = i; 81 | } 82 | if (line.end < min) { 83 | bounds.min = i+1; 84 | } 85 | if (line.start > min || (line.end >= min && line.start < min)) { 86 | bounds.max = i; 87 | } 88 | } 89 | 90 | } 91 | 92 | // Usually first line/char are refered as 1 instead of 0 93 | for (i in bounds.min...linesInfo.length) { 94 | var line = linesInfo[i]; 95 | if (line.start <= min && line.end >= max) { 96 | pos.lines.push({ number: line.number +1, start: min-line.start +1, end: max-line.start +1 }); 97 | break; 98 | } 99 | if (line.start <= min && min <= line.end) { 100 | pos.lines.push({ number: line.number +1, start: min-line.start +1, end: line.end +1 }); 101 | } 102 | if (line.start <= max && max <= line.end) { 103 | pos.lines.push({ number: line.number +1, start: line.start +1, end: max-line.start +1 }); 104 | } 105 | if (line.start >= max || line.end >= max) { 106 | break; 107 | } 108 | } 109 | 110 | return pos; 111 | } 112 | 113 | public inline function revert(position:Position) : hxjsonast.Position { 114 | return {file:position.file, min:position.min - 1, max:position.max - 1}; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tests/MapTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import json2object.utils.JsonSchemaWriter; 28 | import utest.Assert; 29 | 30 | typedef MapStruct = { 31 | var i : Int; 32 | } 33 | 34 | class MapTest implements utest.ITest { 35 | public function new () {} 36 | 37 | public function test1 () { // Str -> Str 38 | var parser = new JsonParser>(); 39 | var writer = new JsonWriter>(); 40 | var data = parser.fromJson('{ "key1": "value1", "key2": "value2" }', "test"); 41 | Assert.equals("value1", data.get("key1")); 42 | Assert.equals("value2", data.get("key2")); 43 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 44 | } 45 | 46 | public function test2 () { // Int -> Str 47 | var parser = new JsonParser>(); 48 | var writer = new JsonWriter>(); 49 | var data = parser.fromJson('{ "1": "value1", "2": "value2" }', "test"); 50 | Assert.equals("value1", data.get(1)); 51 | Assert.equals("value2", data.get(2)); 52 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 53 | } 54 | 55 | public function test3 () { // Str -> Object/Struct 56 | var parser = new JsonParser>(); 57 | var writer = new JsonWriter>(); 58 | var data = parser.fromJson('{ "key1": null, "key2": {"i":9}, "key3":{"i":0} }', "test"); 59 | Assert.isNull(data.get("key1")); 60 | Assert.equals(9, data.get("key2").i); 61 | Assert.equals(0, data.get("key3").i); 62 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 63 | } 64 | 65 | public function test4 () { // Str -> Map 66 | var parser = new JsonParser>>(); 67 | var writer = new JsonWriter>>(); 68 | var data = parser.fromJson('{ "key1": {}, "key2": {"i":"9"}, "key3":{"a":"0"} }', "test"); 69 | Assert.same(new Map(), data.get("key1")); 70 | Assert.equals("9", data.get("key2").get("i")); 71 | Assert.equals("0", data.get("key3").get("a")); 72 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 73 | } 74 | 75 | public function test5 () { // Str -> Array 76 | var parser = new JsonParser>>(); 77 | var writer = new JsonWriter>>(); 78 | var data = parser.fromJson('{ "key1": [], "key2": ["i","9"], "key3":["a"] }', "test"); 79 | Assert.same([], data.get("key1")); 80 | Assert.same(["i","9"], data.get("key2")); 81 | Assert.same(["a"], data.get("key3")); 82 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 83 | } 84 | 85 | #if !lua 86 | public function test6 () { // Schema writer 87 | var schema = new JsonSchemaWriter>().schema; 88 | 89 | var oracle = '{"$$schema": "http://json-schema.org/draft-07/schema#","$$ref": "#/definitions/Map","definitions": {"Map": {"patternProperties": {"/^[-+]?\\d+([Ee][+-]?\\d+)?$/": {"type": "boolean"}},"type": "object"}}}'; 90 | 91 | Assert.isTrue(JsonComparator.areSame(oracle, schema)); 92 | } 93 | #end 94 | } 95 | -------------------------------------------------------------------------------- /tests/ObjectTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import json2object.utils.JsonSchemaWriter; 28 | import utest.Assert; 29 | 30 | enum A { 31 | First; 32 | Second; 33 | } 34 | 35 | typedef ObjectStruct = { 36 | var i : Int; 37 | } 38 | 39 | class ObjectTestData { 40 | 41 | @:jignored 42 | var a:A; 43 | #if (haxe_ver>=4) 44 | @:default(0)final b : Int = 0; 45 | #else 46 | var b(default, never):Int = 0; 47 | #end 48 | 49 | @:optional 50 | public var objTest:ObjectTestData; 51 | 52 | @:default(true) public var base:Bool; 53 | public var map:Map>; 54 | public var struct:ObjectStruct; 55 | var array:Array; 56 | 57 | public var array_array:Array>; 58 | public var array_map:Array>; 59 | public var array_obj:Array>; 60 | 61 | public var foo(default, null) : Int; 62 | } 63 | 64 | class ObjectTest implements utest.ITest { 65 | public function new () {} 66 | 67 | public function test1 () { // Optional/Jignored + missing 68 | var parser = new JsonParser>(); 69 | var writer = new JsonWriter>(); 70 | var data:ObjectTestData = parser.fromJson('{ "base": true, "array": [0,2], "map":{"key":{"base":false, "array":[1], "map":{"t":null}, "struct":{"i": 9}}}, "struct":{"i":1}, "foo": 25 }', "test"); 71 | 72 | Assert.isTrue(data.base); 73 | Assert.same([0,2], @:privateAccess data.array); 74 | Assert.isFalse(data.map.get("key").base); 75 | Assert.same([1], @:privateAccess data.map.get("key").array); 76 | Assert.isNull(data.map.get("key").map.get("t")); 77 | Assert.equals(1, data.struct.i); 78 | Assert.equals(25, data.foo); 79 | 80 | for (error in parser.errors) { 81 | switch (error) { 82 | case UninitializedVariable(v, pos): 83 | Assert.same({file:"test", lines:[{start:pos.min, end:pos.min, number:1}], min:pos.min, max:pos.min}, pos); 84 | switch (pos.min) { 85 | case 110: 86 | Assert.isTrue(["array_array", "array_map", "array_obj", "b", "foo"].lastIndexOf(v) != -1); 87 | case 142: 88 | Assert.isTrue(["array_array", "array_map", "array_obj", "b"].lastIndexOf(v) != -1); 89 | default: 90 | Assert.isTrue(false); 91 | } 92 | default: 93 | Assert.isTrue(false); 94 | } 95 | } 96 | Assert.equals(9, parser.errors.length); 97 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 98 | } 99 | 100 | public function test2 () { // Optional 101 | var parser = new JsonParser>(); 102 | var writer = new JsonWriter>(); 103 | var data = parser.fromJson('{ "objTest":{"b": 2, "base":true, "array":[], "map":{}, "struct":{"i":10}, "array_array":null, "array_map":null, "array_obj":null, "foo":45}, "array": null, "map":{"key":null}, "struct":{"i":2}, "array_array":[[0,1], [4, -1]], "array_map":[{"a":1}, null], "array_obj":[{"base":true, "array":[], "map":{}, "struct":{"i":10}, "array_array":null, "array_map":null, "array_obj":null, "foo":46}], "foo": 63 }', "test"); 104 | Assert.isTrue(data.base); 105 | Assert.isNull(@:privateAccess data.array); 106 | Assert.isNull(data.map.get("key")); 107 | Assert.equals(2, data.struct.i); 108 | Assert.isTrue(data.objTest.base); 109 | Assert.same([], @:privateAccess data.objTest.array); 110 | Assert.same(new Map>(), data.objTest.map); 111 | Assert.equals(63, data.foo); 112 | #if (haxe_ver>=4) 113 | @:privateAccess { 114 | Assert.equals(2, data.objTest.b); 115 | } 116 | #end 117 | 118 | for (error in parser.errors) { 119 | switch (error) { 120 | case UninitializedVariable(v, pos): 121 | Assert.same({file:"test", lines:[{start:pos.min, end:pos.min, number:1}], min:pos.min, max:pos.min}, pos); 122 | switch (pos.min) { 123 | case 390: 124 | Assert.equals("b", v); 125 | case 404: 126 | Assert.isTrue(v == "b" || v == "base"); 127 | default: 128 | Assert.isTrue(false); 129 | } 130 | default: 131 | Assert.isTrue(false); 132 | } 133 | } 134 | 135 | Assert.equals(3, parser.errors.length); 136 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/json2object/writer/StringUtils.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | package json2object.writer; 23 | 24 | import StringTools; 25 | 26 | /** 27 | * Taken from https://github.com/HaxeFoundation/haxe/blob/875ad19432abc2cec6b345cc49a880f5c7f3c98a/std/haxe/format/JsonPrinter.hx 28 | */ 29 | class StringUtils { 30 | #if (haxe_ver >= 4) 31 | public static function quote(s:String) : String { 32 | #if neko 33 | if( s.length != neko.Utf8.length(s) ) { 34 | return quoteUtf8(s); 35 | } 36 | #end 37 | 38 | var buf : #if flash flash.utils.ByteArray #else StringBuf #end; 39 | #if flash 40 | buf = new flash.utils.ByteArray(); 41 | buf.endian = flash.utils.Endian.BIG_ENDIAN; 42 | buf.position = 0; 43 | #else 44 | buf = new StringBuf(); 45 | #end 46 | inline function addChar (c:Int) { 47 | #if flash 48 | buf.writeByte(c); 49 | #else 50 | buf.addChar(c); 51 | #end 52 | } 53 | inline function add (v:String) { 54 | #if flash 55 | // argument is not always a string but will be automatically casted 56 | buf.writeUTFBytes(v); 57 | #else 58 | buf.add(v); 59 | #end 60 | } 61 | 62 | addChar('"'.code); 63 | var i = 0; 64 | #if hl 65 | var prev = -1; 66 | #end 67 | while( true ) { 68 | var c = StringTools.fastCodeAt(s, i++); 69 | if( StringTools.isEof(c) ) break; 70 | switch( c ) { 71 | case '"'.code: add('\\"'); 72 | case '\\'.code: add('\\\\'); 73 | case '\n'.code: add('\\n'); 74 | case '\r'.code: add('\\r'); 75 | case '\t'.code: add('\\t'); 76 | case 8: add('\\b'); 77 | case 12: add('\\f'); 78 | default: 79 | #if flash 80 | if( c >= 128 ) add(String.fromCharCode(c)) else addChar(c); 81 | #elseif hl 82 | if( prev >= 0 ) { 83 | if( c >= 0xD800 && c <= 0xDFFF ) { 84 | addChar( (((prev - 0xD800) << 10) | (c - 0xDC00)) + 0x10000 ); 85 | prev = -1; 86 | } else { 87 | addChar("□".code); 88 | prev = c; 89 | } 90 | } else { 91 | if( c >= 0xD800 && c <= 0xDFFF ) 92 | prev = c; 93 | else 94 | addChar(c); 95 | } 96 | #else 97 | addChar(c); 98 | #end 99 | } 100 | } 101 | #if hl 102 | if( prev >= 0 ) addChar("□".code); 103 | #end 104 | addChar('"'.code); 105 | return buf.toString(); 106 | } 107 | 108 | #if neko 109 | static function quoteUtf8( s : String ) { 110 | var u = new neko.Utf8(); 111 | neko.Utf8.iter(s,function(c) { 112 | switch( c ) { 113 | case '\\'.code, '"'.code: u.addChar('\\'.code); u.addChar(c); 114 | case '\n'.code: u.addChar('\\'.code); u.addChar('n'.code); 115 | case '\r'.code: u.addChar('\\'.code); u.addChar('r'.code); 116 | case '\t'.code: u.addChar('\\'.code); u.addChar('t'.code); 117 | case 8: u.addChar('\\'.code); u.addChar('b'.code); 118 | case 12: u.addChar('\\'.code); u.addChar('f'.code); 119 | default: u.addChar(c); 120 | } 121 | }); 122 | var buf = new StringBuf(); 123 | buf.add('"'); 124 | buf.add(u.toString()); 125 | buf.add('"'); 126 | return buf.toString(); 127 | } 128 | #end 129 | #else 130 | public static function quote(s:String) : String { 131 | #if (neko || php || cpp) 132 | if( s.length != haxe.Utf8.length(s) ) { 133 | return quoteUtf8(s); 134 | } 135 | #end 136 | var buffer = new StringBuf(); 137 | buffer.add('"'); 138 | var i = 0; 139 | while( true ) { 140 | var c = StringTools.fastCodeAt(s, i++); 141 | if( StringTools.isEof(c) ) break; 142 | switch( c ) { 143 | case '"'.code: buffer.add('\\"'); 144 | case '\\'.code: buffer.add('\\\\'); 145 | case '\n'.code: buffer.add('\\n'); 146 | case '\r'.code: buffer.add('\\r'); 147 | case '\t'.code: buffer.add('\\t'); 148 | case 8: buffer.add('\\b'); 149 | case 12: buffer.add('\\f'); 150 | default: 151 | #if flash 152 | if( c >= 128 ) buffer.add(String.fromCharCode(c)) else buffer.addChar(c); 153 | #else 154 | buffer.addChar(c); 155 | #end 156 | } 157 | } 158 | buffer.add('"'); 159 | return buffer.toString(); 160 | } 161 | 162 | #if (neko || php || cpp) 163 | static function quoteUtf8( s : String ) { 164 | var u = new haxe.Utf8(); 165 | haxe.Utf8.iter(s,function(c) { 166 | switch( c ) { 167 | case '\\'.code, '"'.code: u.addChar('\\'.code); u.addChar(c); 168 | case '\n'.code: u.addChar('\\'.code); u.addChar('n'.code); 169 | case '\r'.code: u.addChar('\\'.code); u.addChar('r'.code); 170 | case '\t'.code: u.addChar('\\'.code); u.addChar('t'.code); 171 | case 8: u.addChar('\\'.code); u.addChar('b'.code); 172 | case 12: u.addChar('\\'.code); u.addChar('f'.code); 173 | default: u.addChar(c); 174 | } 175 | }); 176 | return '"' + u.toString() + '"'; 177 | } 178 | #end 179 | #end 180 | } -------------------------------------------------------------------------------- /tests/CustomTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import hxjsonast.Printer; 26 | import haxe.Json; 27 | import json2object.JsonParser; 28 | import json2object.JsonWriter; 29 | import utest.Assert; 30 | 31 | class CustomNum { 32 | @:jcustomwrite(tests.CustomTest.CustomNum.CustomWriteInt) 33 | @:jcustomparse(tests.CustomTest.CustomNum.CustomParseInt) 34 | public var value:Int; 35 | 36 | @:jcustomwrite(tests.CustomTest.CustomNum.CustomWriteString) 37 | @:jcustomparse(tests.CustomTest.CustomNum.CustomParseString) 38 | @:optional 39 | @:default("0") 40 | public var opt_value:String; 41 | 42 | public var control:Int; 43 | 44 | public static var _prefix = "The Number "; 45 | 46 | public static function CustomWriteInt(o:Int):String { 47 | return '"$_prefix$o"'; 48 | } 49 | 50 | public static function CustomWriteString(o:String):String { 51 | return '$o'; 52 | } 53 | 54 | public static function CustomParseInt(val:hxjsonast.Json, name:String):Int { 55 | switch (val.value) { 56 | case JString(s): 57 | var str = StringTools.replace(s, _prefix, ""); 58 | return Std.parseInt(str); 59 | default: 60 | throw 'Unexpected value for $name'; 61 | } 62 | } 63 | 64 | public static function CustomParseString(val:hxjsonast.Json, name:String):String { 65 | switch (val.value) { 66 | case JNumber(s): 67 | return s; 68 | default: 69 | throw 'Unexpected value for $name'; 70 | } 71 | } 72 | } 73 | 74 | @:jcustomparse(tests.CustomTest.WrappedDynamic.CustomParse) 75 | @:jcustomwrite(tests.CustomTest.WrappedDynamic.CustomWrite) 76 | class WrappedDynamic { 77 | @:jignored 78 | public var value:Dynamic; 79 | 80 | public function new() {} 81 | 82 | public static function CustomWrite(o:WrappedDynamic):String { 83 | return '${Json.stringify(o.value)}'; 84 | } 85 | 86 | public static function CustomParse(val:hxjsonast.Json, name:String):WrappedDynamic { 87 | var str = Printer.print(val); 88 | var w = new WrappedDynamic(); 89 | w.value = Json.parse(str); 90 | return w; 91 | } 92 | } 93 | 94 | class NestedTest { 95 | public var num:CustomNum; 96 | public var dyn:WrappedDynamic; 97 | 98 | public function new() {} 99 | } 100 | 101 | class CustomTest implements utest.ITest { 102 | public function new() {} 103 | 104 | public function test1() { 105 | var parser = new JsonParser(); 106 | var writer = new JsonWriter(); 107 | var data = parser.fromJson('{"value": "The Number 42", "control":123}'); 108 | Assert.equals(0, parser.errors.length); 109 | Assert.equals(42, data.value); 110 | Assert.equals("0", data.opt_value); 111 | Assert.equals(123, data.control); 112 | Assert.same(data, parser.fromJson(writer.write(data))); 113 | } 114 | 115 | public function test2() { 116 | var parser = new JsonParser(); 117 | var writer = new JsonWriter(); 118 | var data = parser.fromJson('{"foo": 1, "bar":"two"}'); 119 | Assert.equals(0, parser.errors.length); 120 | Assert.equals(1, data.value.foo); 121 | Assert.equals("two", data.value.bar); 122 | Assert.same(data, parser.fromJson(writer.write(data))); 123 | } 124 | 125 | public function test3() { 126 | var parser = new JsonParser(); 127 | var writer = new JsonWriter(); 128 | var data = parser.fromJson('{"dyn":{"foo": 1, "bar":"two"}, "num":{"value": "The Number 42", "control":123}}'); 129 | Assert.equals(0, parser.errors.length); 130 | Assert.equals(1, data.dyn.value.foo); 131 | Assert.equals(42, data.num.value); 132 | Assert.same(data, parser.fromJson(writer.write(data))); 133 | } 134 | 135 | public function test4() { 136 | var parser = new JsonParser(); 137 | var data = parser.fromJson('{"value": 1, "control":123}'); 138 | Assert.equals(2, parser.errors.length); 139 | Assert.equals(0, data.value); 140 | Assert.equals("0", data.opt_value); 141 | Assert.equals(123, data.control); 142 | } 143 | 144 | public function test5() { 145 | var parser = new JsonParser(); 146 | var writer = new JsonWriter(); 147 | var data = parser.fromJson('{"value": "The Number 62", "opt_value": 71, "control": 12}'); 148 | Assert.equals(0, parser.errors.length); 149 | Assert.equals(62, data.value); 150 | Assert.equals("71", data.opt_value); 151 | Assert.equals(12, data.control); 152 | Assert.same(data, parser.fromJson(writer.write(data))); 153 | } 154 | 155 | public function test6() { 156 | var parser = new JsonParser(); 157 | var data = parser.fromJson('{"value": "The Number 62", "opt_value": "4564", "control": 12}'); 158 | Assert.equals(1, parser.errors.length); 159 | 160 | switch (parser.errors[0]) { 161 | case CustomFunctionException(e, _): 162 | Assert.equals(e, "Unexpected value for opt_value"); 163 | default: 164 | Assert.isTrue(false); 165 | } 166 | 167 | Assert.equals(62, data.value); 168 | Assert.equals("0", data.opt_value); 169 | Assert.equals(12, data.control); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/json2object/utils/TypeTools.hx: -------------------------------------------------------------------------------- 1 | package json2object.utils; 2 | 3 | import haxe.ds.Option; 4 | import haxe.macro.Context; 5 | import haxe.macro.Expr; 6 | import haxe.macro.Type; 7 | import haxe.macro.TypeTools as StdTypeTools; 8 | 9 | /** 10 | Taken from https://github.com/haxetink/tink_macro/blob/296cc6c8610c5638a4f3b4a0f52d715ce74dcc30/src/tink/macro/Sisyphus.hx 11 | and https://github.com/haxetink/tink_macro/blob/296cc6c8610c5638a4f3b4a0f52d715ce74dcc30/src/tink/macro/Types.hx 12 | by Juraj Kirchheim MIT Licenced https://github.com/haxetink/tink_macro/blob/master/LICENSE 13 | **/ 14 | class TypeTools { 15 | #if (macro || display) 16 | 17 | public static inline function follow(type:Type, ?once:Bool):Type { 18 | return StdTypeTools.follow(type, once); 19 | } 20 | 21 | public static inline function followWithAbstracts(type:Type, once:Bool=false):Type { 22 | return StdTypeTools.followWithAbstracts(type, once); 23 | } 24 | 25 | public static inline function applyTypeParameters(type:Type, typeParameters:Array, concreteTypes:Array):Type { 26 | return StdTypeTools.applyTypeParameters(type, typeParameters, concreteTypes); 27 | } 28 | 29 | public static inline function toString(type:Type):String { 30 | return StdTypeTools.toString(type); 31 | } 32 | 33 | public static inline function unify(t1:Type, t2:Type):Bool { 34 | return StdTypeTools.unify(t1, t2); 35 | } 36 | 37 | #end 38 | 39 | #if macro 40 | 41 | public static inline function toComplexType(type:Null):Null { 42 | return { 43 | inline function direct() 44 | return StdTypeTools.toComplexType(type); 45 | 46 | switch (type) { 47 | case null: 48 | null; 49 | case TEnum(_.get().isPrivate => true, _): direct(); 50 | case TInst(_.get().isPrivate => true, _): direct(); 51 | case TType(_.get().isPrivate => true, _): direct(); 52 | case TAbstract(_.get().isPrivate => true, _): direct(); 53 | case TMono(_): direct(); 54 | case TEnum(_.get() => baseType, params): 55 | TPath(toTypePath(baseType, params)); 56 | case TInst(_.get() => classType, params): 57 | switch (classType.kind) { 58 | case KTypeParameter(_): 59 | var ct = asComplexType(classType.name); 60 | switch toType(ct) { 61 | case Some(TInst(_.get() => cl, _)) if (cl.kind.match(KTypeParameter(_)) && cl.module == classType.module && cl.pack.join('.') == classType.pack.join('.')): 62 | ct; 63 | default: 64 | direct(); 65 | } 66 | default: 67 | TPath(toTypePath(classType, params)); 68 | } 69 | case TType(_.get() => baseType, params): 70 | TPath(toTypePath(baseType, params)); 71 | case TFun(args, ret): 72 | TFunction([ for (a in args) a.opt ? nullable(toComplexType(a.t)) : toComplexType(a.t) ], toComplexType(ret)); 73 | case TAnonymous(_.get() => { fields: fields }): 74 | TAnonymous([ for (cf in fields) toField(cf) ]); 75 | case TDynamic(t): 76 | if (t == null) { 77 | macro : Dynamic; 78 | } else { 79 | var ct = toComplexType(t); 80 | macro : Dynamic<$ct>; 81 | } 82 | case TLazy(f): 83 | toComplexType(f()); 84 | case TAbstract(_.get() => baseType, params): 85 | TPath(toTypePath(baseType, params)); 86 | default: 87 | throw "Invalid type"; 88 | } 89 | } 90 | } 91 | 92 | static function toTypePath(baseType:BaseType, params:Array):TypePath { 93 | return { 94 | var module = baseType.module; 95 | var name = module.substring(module.lastIndexOf(".") + 1); 96 | var sub = switch baseType.name { 97 | case _ == name => true: null; 98 | case v: v; 99 | } 100 | 101 | { 102 | pack: baseType.pack, 103 | name: name, 104 | sub: sub, 105 | params: [for (t in params) switch t { 106 | case TInst(_.get().kind => KExpr(e), _): TPExpr(e); 107 | default: TPType(toComplexType(t)); 108 | }], 109 | } 110 | } 111 | } 112 | 113 | static inline function asComplexType(s:String, ?params) 114 | { 115 | return TPath(asTypePath(s, params)); 116 | } 117 | 118 | static function asTypePath(s:String, ?params):TypePath { 119 | var parts = s.split('.'); 120 | var name = parts.pop(), 121 | sub = null; 122 | if (parts.length > 0 && parts[parts.length - 1].charCodeAt(0) < 0x5B) { 123 | sub = name; 124 | name = parts.pop(); 125 | if(sub == name) sub = null; 126 | } 127 | return { 128 | name: name, 129 | pack: parts, 130 | params: params == null ? [] : params, 131 | sub: sub 132 | }; 133 | } 134 | 135 | static function toType(t:ComplexType, ?pos:Position):Option { 136 | if (pos == null) pos = Context.currentPos(); 137 | return try { 138 | Some(Context.typeof(macro @:pos(pos) { 139 | var v:$t = null; 140 | v; 141 | })); 142 | } catch (_:Dynamic) { 143 | None; 144 | } 145 | } 146 | 147 | static function nullable(complexType:ComplexType):ComplexType return macro : Null<$complexType>; 148 | 149 | static function toField(cf : ClassField) : Field return { 150 | function varAccessToString(va : VarAccess, getOrSet : String) : String return { 151 | switch (va) { 152 | case AccNormal: "default"; 153 | case AccNo: "null"; 154 | case AccNever: "never"; 155 | case AccResolve: throw "Invalid TAnonymous"; 156 | case AccCall: getOrSet; 157 | case AccInline: "default"; 158 | case AccRequire(_, _): "default"; 159 | default: throw "not implemented"; 160 | } 161 | } 162 | if (cf.params.length == 0) { 163 | name: cf.name, 164 | doc: cf.doc, 165 | access: 166 | (cf.isPublic ? [ APublic ] : [ APrivate ]) 167 | #if haxe4 .concat(if (cf.isFinal) [AFinal] else []) #end 168 | , 169 | kind: switch([ cf.kind, cf.type ]) { 170 | #if haxe4 171 | case [ FVar(_, _), ret ] if (cf.isFinal): 172 | FVar(toComplexType(ret), null); 173 | #end 174 | case [ FVar(read, write), ret ]: 175 | FProp( 176 | varAccessToString(read, "get"), 177 | varAccessToString(write, "set"), 178 | toComplexType(ret), 179 | null 180 | ); 181 | case [ FMethod(_), TFun(args, ret) ]: 182 | FFun({ 183 | args: [ 184 | for (a in args) { 185 | name: a.name, 186 | opt: a.opt, 187 | type: toComplexType(a.t), 188 | } 189 | ], 190 | ret: toComplexType(ret), 191 | expr: null, 192 | }); 193 | default: 194 | throw "Invalid TAnonymous"; 195 | }, 196 | pos: cf.pos, 197 | meta: cf.meta.get(), 198 | } else { 199 | throw "Invalid TAnonymous"; 200 | } 201 | } 202 | 203 | #end 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json2object - Type safe Haxe/JSON (de)serializer 2 | 3 | [![CI status](https://github.com/elnabo/json2object/actions/workflows/main.yaml/badge.svg)](https://github.com/elnabo/json2object/actions/workflows/main.yaml) 4 | 5 | This library uses macro and a typed position aware JSON parsing (hxjsonast : ) to create json parser and writer from and to every supported type. 6 | 7 | Incorrect json files or mismatch between the object and the json will yield errors or exceptions, with information on the position of the problematic parts. 8 | 9 | Requires at least haxe 3.4.1. 10 | 11 | ## Installation 12 | 13 | ``` 14 | haxelib install json2object 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Using the parser 20 | ```haxe 21 | var parser = new json2object.JsonParser(); // Creating a parser for Cls class 22 | parser.fromJson(jsonString, filename); // Parsing a string. A filename is specified for errors management 23 | var data:Cls = parser.value; // Access the parsed class 24 | var errors:Array = parser.errors; // Access the potential errors yield during the parsing 25 | ``` 26 | 27 | It is also possible to populate an existing Array with the errors 28 | ```haxe 29 | var errors = new Array(); 30 | var data:Cls = new json2object.JsonParser(errors).fromJson(jsonString, filename); 31 | ``` 32 | 33 | To print the errors, you can do 34 | ```haxe 35 | trace(json2object.ErrorUtils.convertErrorArray(parser.errors)); 36 | ``` 37 | 38 | ### Using the writer 39 | ```haxe 40 | var value:Cls; 41 | var writer = new json2object.JsonWriter(); // Creating a writer for Cls class 42 | var json = writer.write(value); 43 | ``` 44 | 45 | The `write` function accepts an optional `String` parameter for indenting the json file. 46 | 47 | ### Using the JsonSchema writer 48 | ```haxe 49 | var schema = new json2object.utils.JsonSchemaWriter().schema; 50 | ``` 51 | 52 | The constructor accepts an optional `String` parameter for indenting the schema. The generated schema follow null-safety rules. 53 | 54 | An other parser `json2object.utils.special.VSCodeSchemaWriter` has been introduced in 3.6.3 to produce a schema with some non standard properties used by VScode. 55 | 56 | ### Constraints in the parsing 57 | 58 | - Variables defined with the `@:jignored` metadata will be ignored by the parser. 59 | - Variables defined with the `@:optional` metadata won't trigger errors if missing. 60 | 61 | ### Supported types 62 | 63 | - Basic types (`Int`, `Float`, `Bool`, `String`) 64 | - `Null` and `Array` 65 | - `Map` with `Int` or `String` keys 66 | - Class (generics are supported) 67 | - Anonymous structure 68 | - Typedef alias of supported types 69 | - Enum values 70 | - Abstract over a supported type 71 | - Abstract enum of String, Int, Float or Bool 72 | 73 | ### Other 74 | 75 | - As of version 2.4.0, the parser fields `warnings` and `object` have been replaced by `errors` and `value` respectively. Since version `3.6.1`, previous notations are no longer supported. 76 | 77 | - Anonymous structure variables can be defined to be loaded with a default value if none is specified in the json using the `@:default` metadata 78 | ```haxe 79 | typedef Struct = { 80 | var normal:String; 81 | 82 | @:default(new Map()) 83 | var map:Map; 84 | 85 | @:default(-1) @:optional 86 | var id:Int; 87 | } 88 | ``` 89 | 90 | - `@:default(auto)` will, by default, initialize each field of the anonymous structure / object to its default value. No effect on non Structure/Object variables. 91 | 92 | - Variable defined as `(default, null)` may have unexpected behaviour on some `extern` classes. 93 | 94 | - You can alias a field from the json into another name, for instance if the field name isn't a valid haxe identifier. 95 | ```haxe 96 | typedef Struct = { 97 | @:alias("public") var isPublic:Bool; 98 | } 99 | class Main { 100 | static function main() { 101 | var parser = new JsonParser(); 102 | var data = parser.fromJson('{"public": true }', "file.json"); 103 | trace(data.isPublic); 104 | } 105 | } 106 | ``` 107 | If multiple alias metadatas are on the variable only the last one is taken into account. 108 | 109 | - As of version 3.4.0, private classes can be parsed except on the CS, Java and HL targets. 110 | 111 | - As of version 3.7.0, it is possible to add field or class specific parser/writer to object using the `@:jcustomparse` / `@:jcustomwrite` meta. This increase the type coverage of json parsing/writing. Those custom parser/writer can also be applied to the entire class. 112 | - The custom writer receive a single parameter, the value to stringify 113 | - The custom parser receive two parameters: the corresponding json, encoded in a `hxjsonast.Json` instance, and the name of the field being parsed. 114 | - The `@:jcustom*` metadatas require the fully quallified path to the custom function, for instance `pack.TheClass.fn` or `pack.TheModule.TheClass.fn` 115 | - As of version 3.8.0 throwing an exception in a custom parser will be available in `parser.errors` in the `CustomFunctionException` member. 116 | ```haxe 117 | class Object { 118 | @:jcustomparse(Object.customParse) 119 | @:jcustomwrite(Object.customWrite) 120 | public var value:Date; 121 | 122 | public function new() {} 123 | 124 | public static function customWrite(v:Date):String { 125 | return v.getTime() + ''; 126 | } 127 | 128 | public static function customParse(val:Json, name:String):Date { 129 | return switch (val.value) { 130 | case JString(s): 131 | Date.fromString(s); 132 | case JNumber(s): 133 | Date.fromTime(Std.parseFloat(s)); 134 | default: 135 | null; 136 | 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | ## Example 143 | 144 | With an anonymous structure: 145 | ```haxe 146 | import json2object.JsonParser; 147 | 148 | class Main { 149 | static function main() { 150 | var parser = new JsonParser<{ name : String, quantity : Int }>(); 151 | var data = parser.fromJson('{"name": "computer", "quantity": 2 }', "file.json"); 152 | trace(data.name, data.quantity); 153 | } 154 | } 155 | ``` 156 | 157 | A more complex example with a class and subclass: 158 | ```haxe 159 | import json2object.JsonParser; 160 | 161 | class Data { 162 | public var a:String; 163 | public var b:SubData; 164 | public var d:Array; 165 | public var e:Array>; 166 | public var f:Array; 167 | public var g:Array; 168 | @:jignored 169 | public var h:Math; 170 | } 171 | 172 | class SubData { 173 | public var c:String; 174 | } 175 | 176 | class Main { 177 | static function main() { 178 | var parser = new JsonParser(); 179 | var data = parser.fromJson('{"a": "a", "b": {"c": "c"}, "e": [ { "c": "1" }, { "c": "2" } ], "f": [], "g": [ true ] }', "file.json"); 180 | var errors = parser.errors; 181 | 182 | trace(data.a); 183 | trace(data.b.c); 184 | 185 | for (e in data.e) { 186 | trace(e.get("c")); 187 | } 188 | 189 | trace(data.e[0].get("c"); 190 | trace(data.f.length); 191 | 192 | for (g in data.g) { 193 | trace(data.g.length); 194 | } 195 | 196 | for (e in errors) { 197 | switch(e) { 198 | case IncorrectType(variable, expected, pos): 199 | case UninitializedVariable(variable, pos): 200 | case UnknownVariable(variable, pos): 201 | default: 202 | } 203 | } 204 | } 205 | } 206 | ``` 207 | -------------------------------------------------------------------------------- /src/json2object/reader/BaseParser.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object.reader; 24 | 25 | import haxe.Constraints.Function; 26 | import json2object.Error; 27 | import json2object.Error.ErrorType; 28 | import json2object.Position; 29 | import json2object.PositionUtils; 30 | 31 | using StringTools; 32 | 33 | #if cs @:nativeGen #end 34 | class BaseParser { 35 | 36 | public var value : T; 37 | 38 | public var errors:Array; 39 | 40 | private var errorType:Error.ErrorType; 41 | 42 | private var putils:PositionUtils; 43 | 44 | private function new(errors:Array, putils:PositionUtils, errorType:ErrorType) { 45 | this.errors = errors; 46 | this.putils = putils; 47 | this.errorType = errorType; 48 | } 49 | 50 | public function fromJson(jsonString:String, filename:String="") : T { 51 | putils = new PositionUtils(jsonString); 52 | errors = []; 53 | try { 54 | var json = hxjsonast.Parser.parse(jsonString, filename); 55 | loadJson(json); 56 | } 57 | catch (e:hxjsonast.Error) { 58 | errors.push(ParserError(e.message, putils.convertPosition(e.pos))); 59 | } 60 | return value; 61 | } 62 | 63 | public function loadJson(json:hxjsonast.Json, variable:String="") : T { 64 | var pos = putils.convertPosition(json.pos); 65 | switch (json.value) { 66 | case JNull : loadJsonNull(pos, variable); 67 | case JString(s) : loadJsonString(s, pos, variable); 68 | case JNumber(n) : loadJsonNumber(n, pos, variable); 69 | case JBool(b) : loadJsonBool(b, pos, variable); 70 | case JArray(a) : loadJsonArray(a, pos, variable); 71 | case JObject(o) : loadJsonObject(o, pos, variable); 72 | } 73 | return value; 74 | } 75 | 76 | private function loadJsonNull(pos:Position, variable:String) { 77 | onIncorrectType(pos, variable); 78 | } 79 | 80 | private function loadJsonString(s:String, pos:Position, variable:String) { 81 | onIncorrectType(pos, variable); 82 | } 83 | 84 | private function loadString(s:String, pos:Position, variable:String, validValues:Array, defaultValue:String):String { 85 | if (validValues.indexOf(s) != -1) { 86 | return s; 87 | } 88 | onIncorrectType(pos, variable); 89 | return defaultValue; 90 | } 91 | 92 | private function loadJsonNumber(f:String, pos:Position, variable:String) { 93 | onIncorrectType(pos, variable); 94 | } 95 | 96 | private function loadJsonUInt(f:String, pos:Position, variable:String, value:UInt):UInt { 97 | var uint:UInt = 0; 98 | f = f.trim(); 99 | var neg = f.charAt(0) == '-'; 100 | if (neg) { 101 | f = f.substr(1); 102 | } 103 | var hex = f.startsWith('0x'); 104 | if (hex) { 105 | f = f.substr(2); 106 | } 107 | 108 | var base = hex ? 16 : 10; 109 | var pow = 1; 110 | var i = f.length - 1; 111 | while (i >= 0) { 112 | var cur = hex ? Std.parseInt('0x${f.charAt(i)}') : Std.parseInt(f.charAt(i)); 113 | if (cur == null) { 114 | onIncorrectType(pos, variable); 115 | return value; 116 | } 117 | uint += pow * cur; 118 | pow *= base; 119 | i--; 120 | } 121 | return uint; 122 | } 123 | 124 | private function loadJsonInt(f:String, pos:Position, variable:String, value:Int):Int { 125 | if (Std.parseInt(f) != null && Std.parseInt(f) == Std.parseFloat(f)) { 126 | return Std.parseInt(f); 127 | } 128 | onIncorrectType(pos, variable); 129 | return value; 130 | } 131 | 132 | private function loadJsonFloat(f:String, pos:Position, variable:String, value:Float):Float { 133 | if (Std.parseInt(f) != null) { 134 | return Std.parseFloat(f); 135 | } 136 | onIncorrectType(pos, variable); 137 | return value; 138 | } 139 | 140 | private function loadJsonBool(b:Bool, pos:Position, variable:String) { 141 | onIncorrectType(pos, variable); 142 | } 143 | 144 | private function loadJsonArray(a:Array, pos:Position, variable:String) { 145 | onIncorrectType(pos, variable); 146 | } 147 | 148 | private function loadJsonArrayValue(a:Array, loadJsonFn:Function, variable:String) { 149 | return [ 150 | for (j in a) 151 | { 152 | try { 153 | loadJsonFn(j, variable); 154 | } catch (e:InternalError) { 155 | if (e != ParsingThrow) { 156 | throw e; 157 | } 158 | 159 | continue; 160 | } 161 | } 162 | ]; 163 | } 164 | 165 | private function loadJsonObject(o:Array, pos:Position, variable:String) { 166 | onIncorrectType(pos, variable); 167 | } 168 | 169 | private function loadObjectField(loadJsonFn:Function, field:hxjsonast.Json.JObjectField, name:String, assigned:Map, defaultValue:Any, pos:Position):Any { 170 | try { 171 | var ret = cast loadJsonFn(field.value, field.name); 172 | mapSet(assigned, name, true); 173 | return ret; 174 | } catch (e:InternalError) { 175 | if (e != ParsingThrow) { 176 | throw e; 177 | } 178 | } 179 | #if cs 180 | // CS sometimes wrap the Haxe errors, unwrap them. 181 | // Could be https://github.com/HaxeFoundation/haxe/issues/6817 182 | catch (e:cs.system.reflection.TargetInvocationException) { 183 | #if (haxe_ver >= 4.1) 184 | var e = cast(e.InnerException, haxe.ValueException).value; 185 | var es = '$e'; 186 | #elseif haxe4 187 | var e = untyped __cs__("((global::haxe.lang.HaxeException)(e.InnerException)).obj"); 188 | var es = e.toString(); 189 | #else 190 | var e = untyped __cs__("((global::haxe.lang.HaxeException)(global::haxe.lang.Exceptions.exception.InnerException)).obj"); 191 | var es = e.toString(); 192 | #end 193 | 194 | if (es != "ParsingThrow") { 195 | errors.push(CustomFunctionException(e, pos)); 196 | } 197 | } 198 | #end 199 | catch (e:Any) { 200 | errors.push(CustomFunctionException(e, pos)); 201 | } 202 | return defaultValue; 203 | } 204 | 205 | private function loadObjectFieldReflect(loadJsonFn:Function, field:hxjsonast.Json.JObjectField, name:String, assigned:Map, pos:Position) { 206 | try { 207 | Reflect.setField(value, name, cast loadJsonFn(field.value, field.name)); 208 | mapSet(assigned, name, true); 209 | } catch (e:InternalError) { 210 | if (e != ParsingThrow) { 211 | throw e; 212 | } 213 | } 214 | catch (e:Any) { 215 | errors.push(CustomFunctionException(e, pos)); 216 | } 217 | } 218 | 219 | private function objectSetupAssign(assigned:Map, keys:Array, values:Array) { 220 | for (i in 0...keys.length) { 221 | mapSet(assigned, keys[i], values[i]); 222 | } 223 | } 224 | 225 | private function objectErrors(assigned:Map, pos:Position) { 226 | var lastPos:Null = putils.convertPosition({file:pos.file, min:pos.max-1, max:pos.max-1}); 227 | for (s in assigned.keys()) { 228 | if (!assigned[s]) { 229 | errors.push(UninitializedVariable(s, lastPos)); 230 | } 231 | } 232 | } 233 | 234 | private function onIncorrectType(pos:Position, variable:String) { 235 | parsingThrow(); 236 | } 237 | 238 | private function parsingThrow() { 239 | if (errorType != NONE) { 240 | throw ParsingThrow; 241 | } 242 | } 243 | 244 | private function objectThrow(pos:Position, variable:String) { 245 | if (errorType == THROW) { 246 | throw ParsingThrow; 247 | } 248 | 249 | if (errorType == OBJECTTHROW) { 250 | errors.push(UninitializedVariable(variable, pos)); 251 | } 252 | } 253 | 254 | private #if !js inline #end function mapSet(map:Map, key:String, value:Bool) { 255 | map.set(key, value); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /tests/StructureTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import json2object.utils.JsonSchemaWriter; 28 | import utest.Assert; 29 | 30 | typedef Struct = { 31 | @:default(true) 32 | var a : Bool; 33 | var b : Int; 34 | } 35 | 36 | typedef DefaultStruct = { 37 | @:optional 38 | //~ @:default({a:true, b:0}) 39 | @:default(auto) 40 | var d : Struct; 41 | @:optional 42 | @:default(new Map()) 43 | var map:Map; 44 | @:optional 45 | var s:String; 46 | } 47 | 48 | typedef ReadonlyStruct = { 49 | var foo(default,null):Int; 50 | } 51 | 52 | typedef SimpleDefault = { 53 | @:default({a:"foo", b:3}) 54 | var foo(default,null):{a:String, b:Int}; 55 | @:default("test") 56 | var auto:String; 57 | @:default(auto) 58 | var bool:Bool; 59 | } 60 | 61 | typedef OuterStruct = { 62 | @:optional var outer:InnerStruct; 63 | } 64 | 65 | typedef InnerStruct = { 66 | @:optional var inner:Int; 67 | } 68 | 69 | typedef ArrayStruct = { 70 | var array:Array; 71 | } 72 | 73 | typedef MapIIStruct = { 74 | var map:Map; 75 | } 76 | 77 | typedef Issue19 = { 78 | @:default(auto) var s:Issue19Inner; 79 | } 80 | 81 | typedef Issue19Inner = { 82 | @:optional @:default(0) var a:Int; 83 | @:optional var b:Int; 84 | @:optional @:default(0) var c:Int; 85 | var d:Issue19Inner; 86 | @:optional @:default(auto) var e:Struct; 87 | @:default(auto) var f:StructA; 88 | } 89 | 90 | class StructA { 91 | @:default(1) public var a:Int; 92 | } 93 | 94 | typedef StructB = { 95 | @:optional @:default(0) var a:Int; 96 | var b:Bool; 97 | @:optional @:default(0) var c:String; 98 | } 99 | 100 | class StructureTest implements utest.ITest { 101 | public function new () {} 102 | 103 | public function test1 () { 104 | var parser = new JsonParser(); 105 | var writer = new JsonWriter(); 106 | var data = parser.fromJson('{}', "test"); 107 | Assert.equals(0, parser.errors.length); 108 | Assert.isTrue(data.d.a); 109 | Assert.equals(0, data.d.b); 110 | Assert.same(new Map(), data.map); 111 | Assert.isNull(data.s); 112 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 113 | 114 | data = parser.fromJson('{"d":{"a":false, "b":1}, "map":{"key1":55, "key2": 46, "key3":43}, "s":"sup"}', "test"); 115 | Assert.equals(0, parser.errors.length); 116 | Assert.isFalse(data.d.a); 117 | Assert.equals(1, data.d.b); 118 | Assert.equals(46, data.map["key2"]); 119 | Assert.equals("sup", data.s); 120 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 121 | } 122 | 123 | public function test2 () { 124 | var parser = new JsonParser(); 125 | var writer = new JsonWriter(); 126 | var data = parser.fromJson('{ "a": true, "b": 12 }', "test"); 127 | Assert.isTrue(data.a); 128 | Assert.equals(12, data.b); 129 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 130 | } 131 | 132 | public function test3 () { 133 | var parser = new JsonParser(); 134 | var writer = new JsonWriter(); 135 | var data = parser.fromJson('{ "a": 12, "b": 12 }', "test"); 136 | Assert.isTrue(data.a); 137 | Assert.equals(2, parser.errors.length); // IncorrectType + UninitializedVariable 138 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 139 | } 140 | 141 | public function test4 () { 142 | var parser = new JsonParser(); 143 | var writer = new JsonWriter(); 144 | var data = parser.fromJson('{"foo":1}', ""); 145 | Assert.equals(1, data.foo); 146 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 147 | } 148 | 149 | public function test5 () { 150 | var parser = new JsonParser(); 151 | var writer = new JsonWriter(); 152 | var data = parser.fromJson('{"foo":1.2}', ""); 153 | Assert.equals(parser.errors.length, 2); 154 | Assert.equals(0, data.foo); 155 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 156 | } 157 | 158 | public function test6 () { 159 | var parser = new JsonParser<{ var foo(default,null):Int; }>(); 160 | var writer = new JsonWriter<{ var foo(default,null):Int; }>(); 161 | var data = parser.fromJson('{"foo":12}', ""); 162 | Assert.equals(12, data.foo); 163 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 164 | } 165 | 166 | public function test7 () { 167 | var parser = new JsonParser(); 168 | var writer = new JsonWriter(); 169 | var data = parser.fromJson('{"outer": {}}', ""); 170 | // @:optionnal transform Int into Null 171 | Assert.isNull(data.outer.inner); 172 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 173 | } 174 | 175 | public function test8 () { 176 | var parser = new JsonParser(); 177 | var writer = new JsonWriter(); 178 | var data = parser.fromJson('{"array": [1,2,3.2]}', ""); 179 | Assert.equals(1, parser.errors.length); 180 | Assert.same([1,2], data.array); 181 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 182 | } 183 | 184 | public function test9 () { 185 | var parser = new JsonParser(); 186 | var writer = new JsonWriter(); 187 | var data = parser.fromJson('{"map": {"1":2, "3.1": 4, "5":6, "7":8.2}}', ""); 188 | Assert.equals(2, parser.errors.length); 189 | Assert.equals(2, data.map.get(1)); 190 | Assert.equals(6, data.map.get(5)); 191 | Assert.isFalse(data.map.exists(7)); 192 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 193 | } 194 | 195 | public function test10 () { 196 | var parser = new JsonParser(); 197 | var writer = new JsonWriter(); 198 | var data = parser.fromJson('{}', ""); 199 | Assert.equals(1, parser.errors.length); 200 | Assert.equals(0, data.s.a); 201 | Assert.isNull(data.s.b); 202 | Assert.equals(0, data.s.c); 203 | Assert.isNull(data.s.d); 204 | Assert.isTrue(data.s.e.a); 205 | Assert.equals(0, data.s.e.b); 206 | Assert.equals(1, data.s.f.a); 207 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 208 | } 209 | 210 | public function test11 () { 211 | var parser = new JsonParser(); 212 | var writer = new JsonWriter(); 213 | var data = parser.fromJson('{"s":{"a":1, "b":2, "c":3, "d":null, "e":{"a":false,"b":1}, "f":{"a":2}}}', ""); 214 | Assert.equals(0, parser.errors.length); 215 | Assert.equals(1, data.s.a); 216 | Assert.equals(2, data.s.b); 217 | Assert.equals(3, data.s.c); 218 | Assert.isNull(data.s.d); 219 | Assert.isFalse(data.s.e.a); 220 | Assert.equals(1, data.s.e.b); 221 | Assert.equals(2, data.s.f.a); 222 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 223 | } 224 | 225 | #if !lua 226 | public function test12 () { 227 | var schema = new JsonSchemaWriter().schema; 228 | var oracle = '{"definitions": {"{ b : Int, a : String }": {"additionalProperties": false,"properties": {"b": {"type": "integer"},"a": {"type": "string"}},"required": ["a","b"],"type": "object"},"tests.SimpleDefault": {"additionalProperties": false,"properties": {"foo": {"$$ref": "#/definitions/{ b : Int, a :String }"},"auto": {"default": "test", "type": "string"},"bool":{"default": false, "type": "boolean"}},"required": ["auto","bool","foo"],"type": "object"}},"$$ref": "#/definitions/tests.SimpleDefault","$$schema": "http://json-schema.org/draft-07/schema#"}'; 229 | Assert.isTrue(JsonComparator.areSame(oracle, schema)); 230 | } 231 | #end 232 | } 233 | -------------------------------------------------------------------------------- /tests/EnumTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.Error; 26 | import json2object.JsonParser; 27 | import json2object.JsonWriter; 28 | import json2object.utils.JsonSchemaWriter; 29 | import utest.Assert; 30 | 31 | enum Color { 32 | Green; 33 | Red; 34 | RGBA(r:Int, g:Int, b:Int, a:Float); 35 | None(r:{a:Int}); 36 | } 37 | 38 | enum WithParam { 39 | First(a:T1); 40 | Second(a:T2); 41 | Both(a:T1, b:T2); 42 | } 43 | 44 | typedef EnumStruct = { 45 | var value : Color; 46 | } 47 | 48 | typedef ArrayEnumStruct = { 49 | var value : Array; 50 | } 51 | 52 | typedef WithParamStruct = { 53 | var value : WithParam; 54 | } 55 | 56 | enum Enum1 { 57 | EnumValue1(value:String); 58 | EnumValue2(errors:String); 59 | EnumValue3(putils:String); 60 | } 61 | 62 | typedef WithDefault = { 63 | @:default(Green) @:optional var value : Color; 64 | } 65 | 66 | typedef WithDefaultOther = { 67 | @:default(VAL1) @:optional var value : tests.OtherEnum.TestEnum; 68 | } 69 | 70 | class EnumTest implements utest.ITest 71 | { 72 | public function new () {} 73 | 74 | public function test1 () 75 | { 76 | var parser = new JsonParser(); 77 | var writer = new JsonWriter(); 78 | var data = parser.fromJson('{"value":"Red"}', "test.json"); 79 | Assert.equals(0, parser.errors.length); 80 | Assert.equals(Red, data.value); 81 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 82 | 83 | data = parser.fromJson('{"value":"RGBA"}', "test.json"); 84 | Assert.equals(2, parser.errors.length); 85 | Assert.isNull(data.value); 86 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 87 | } 88 | 89 | public function test2 () 90 | { 91 | var parser = new JsonParser(); 92 | var writer = new JsonWriter(); 93 | var data = parser.fromJson('{"value":{"Red":{}}}', "test.json"); 94 | Assert.equals(Red, data.value); 95 | Assert.equals(0, parser.errors.length); 96 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 97 | 98 | data = parser.fromJson('{"value":{"RGBA":{"r":25, "g":30, "b":255, "a":0.5}}}', "test.json"); 99 | Assert.equals(0, parser.errors.length); 100 | Assert.isTrue(Type.enumEq(RGBA(25,30,255,0.5),data.value)); 101 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 102 | } 103 | 104 | public function test3 () 105 | { 106 | var parser = new JsonParser(); 107 | var writer = new JsonWriter(); 108 | var data = parser.fromJson('{"value":{"Red":{"a":0.5}}}', "test.json"); 109 | Assert.isNull(data.value); 110 | Assert.equals(2, parser.errors.length); 111 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 112 | } 113 | 114 | public function test4 () 115 | { 116 | var parser = new JsonParser(); 117 | var writer = new JsonWriter(); 118 | var data = parser.fromJson('{"value":{"None":{"a":0.5}}}', "test.json"); 119 | Assert.isNull(data.value); 120 | Assert.equals(2, parser.errors.length); 121 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 122 | } 123 | 124 | public function test5 () 125 | { 126 | var parser = new JsonParser(); 127 | var writer = new JsonWriter(); 128 | var data = parser.fromJson('{"value":{"None":{"r":{"a":25}}}}', "test.json"); 129 | Assert.equals(0, parser.errors.length); 130 | switch (data.value) { 131 | case None(r): 132 | Assert.equals(25, r.a); 133 | default: 134 | Assert.equals(None({a:25}), data.value); 135 | } 136 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 137 | } 138 | 139 | public function test6 () 140 | { 141 | var parser = new JsonParser(); 142 | var writer = new JsonWriter(); 143 | var data = parser.fromJson('{"value":["Red", "Green", "Yellow", {"Red":{}}, {"RGBA":{"r":1, "g":1, "b":0}}, {"RGBA":{"r":1, "g":1, "b":0, "a":0.2}}]}', ""); 144 | Assert.equals(4, data.value.length); 145 | Assert.equals(-1, data.value.indexOf(null)); 146 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 147 | } 148 | 149 | public function test7 () 150 | { 151 | var parser = new JsonParser(); 152 | var writer = new JsonWriter(); 153 | var data = parser.fromJson('{"value":{"First":{"a":"a"}}}', "test.json"); 154 | Assert.equals(0, parser.errors.length); 155 | Assert.isTrue(Type.enumEq(First("a"),data.value)); 156 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 157 | 158 | data = parser.fromJson('{"value":{"Second":{"a":1}}}', "test.json"); 159 | Assert.equals(0, parser.errors.length); 160 | Assert.isTrue(Type.enumEq(Second(1),data.value)); 161 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 162 | 163 | data = parser.fromJson('{"value":{"Both":{"a":"a","b":1}}}', "test.json"); 164 | Assert.equals(0, parser.errors.length); 165 | Assert.isTrue(Type.enumEq(Both("a",1),data.value)); 166 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 167 | 168 | data = parser.fromJson('{"value":{"Both":{"b":1,"a":"a"}}}', "test.json"); 169 | Assert.equals(0, parser.errors.length); 170 | Assert.isTrue(Type.enumEq(Both("a",1),data.value)); 171 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 172 | 173 | data = parser.fromJson('{"value":{"Second":{"b":1}}}', "test.json"); 174 | Assert.same(InvalidEnumConstructor("Second", "WithParam", {file:"test.json", lines:[{number:1, start:10, end:28}], min:10, max:28}), parser.errors[0]); 175 | Assert.same(UninitializedVariable("value", {file:"test.json", lines:[{number:1, start:29, end:29}], min:29, max:29}), parser.errors[1]); 176 | Assert.equals(2, parser.errors.length); 177 | } 178 | 179 | public function test8 () 180 | { 181 | var parser = new JsonParser(); 182 | var writer = new JsonWriter(); 183 | var data = parser.fromJson('{"value":{"First":{"a":1}}}', "test.json"); 184 | Assert.equals(2, parser.errors.length); 185 | Assert.isNull(data.value); 186 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 187 | 188 | data = parser.fromJson('{"value":{"Second":{"b":"1"}}}', "test.json"); 189 | Assert.equals(2, parser.errors.length); 190 | Assert.isNull(data.value); 191 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 192 | } 193 | 194 | /** Issue 32 */ 195 | public function test9 () 196 | { 197 | var writer = new JsonWriter(); 198 | var json:String = writer.write(EnumValue1("test")); 199 | Assert.equals(json,'{"EnumValue1": {"value": "test"}}'); 200 | 201 | var parser = new JsonParser(); 202 | parser.fromJson(json, ''); 203 | Assert.same(parser.errors, []); 204 | Assert.same(parser.value, EnumValue1("test")); 205 | 206 | var parser = new JsonParser(); 207 | parser.fromJson('{"EnumValue2": {"errors": "test"}}', ''); 208 | Assert.same(parser.errors, []); 209 | Assert.same(parser.value, EnumValue2("test")); 210 | 211 | var parser = new JsonParser(); 212 | parser.fromJson('{"EnumValue3": {"putils": "test"}}', ''); 213 | Assert.same(parser.errors, []); 214 | Assert.same(parser.value, EnumValue3("test")); 215 | } 216 | 217 | /** Issue 34 **/ 218 | public function test10 () 219 | { 220 | var parser = new JsonParser(); 221 | var writer = new JsonWriter(); 222 | var data = parser.fromJson('{}', "test.json"); 223 | Assert.equals(0, parser.errors.length); 224 | Assert.same(data.value, Color.Green); 225 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 226 | 227 | var parser = new JsonParser(); 228 | var writer = new JsonWriter(); 229 | var data = parser.fromJson('{}', "test.json"); 230 | Assert.equals(0, parser.errors.length); 231 | Assert.same(data.value, tests.OtherEnum.TestEnum.VAL1); 232 | Assert.same(data, parser.fromJson(writer.write(data), "test")); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/json2object/utils/schema/JsonTypeTools.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object.utils.schema; 24 | 25 | import haxe.macro.Context; 26 | import haxe.macro.Expr; 27 | 28 | using StringTools; 29 | using json2object.utils.TypeTools; 30 | using json2object.writer.StringUtils; 31 | 32 | typedef AnonDecls = Map; 33 | 34 | class JsonTypeTools { 35 | #if macro 36 | inline static function str2Expr (str:String) : Expr { 37 | return macro $v{str}; 38 | } 39 | 40 | inline static function nullable (ct:ComplexType) : ComplexType { 41 | return TPath({name: "Null", pack:[], params:[ TPType(ct)], sub:null}); 42 | } 43 | 44 | public static function toExpr(jt:JsonType, parsingType:ParsingType) : Expr { 45 | return _toExpr(jt, '', null, parsingType); 46 | } 47 | 48 | static function declsToAnonDecl (decls:AnonDecls) : Expr { 49 | #if haxe4 50 | return { 51 | expr:EObjectDecl([ for (k=>v in decls) {field:k, expr:v.expr, quotes:Quoted}]), 52 | pos:Context.currentPos() 53 | }; 54 | #else 55 | return { 56 | expr:EObjectDecl([ for (value in decls) value]), 57 | pos:Context.currentPos() 58 | }; 59 | #end 60 | } 61 | 62 | static function sort (a:String, b:String) : Int { 63 | if (a == b) { return 0; } 64 | return (a > b) ? 1 : -1; 65 | } 66 | 67 | static function _toExpr(jt:JsonType, descr:String, defaultValue:Null, parsingType:ParsingType) : Expr { 68 | var decls = new AnonDecls(); 69 | inline function declare(name:String, expr:Expr) { 70 | decls.set(name, {field:name, expr:expr}); 71 | } 72 | 73 | switch (jt) { 74 | case JTNull: 75 | declare("type", macro "null"); 76 | case JTSimple(type): 77 | declare("type", macro $v{type}); 78 | case JTString(s): 79 | declare("const", macro $v{s}); 80 | case JTBool(b): 81 | declare("const_bool", (b) ? macro true : macro false); 82 | case JTFloat(f): 83 | declare("const_float", macro $v{f}); 84 | case JTInt(i): 85 | declare("const_int", macro $v{i}); 86 | case JTObject(properties, rq, defaults): 87 | declare("type", macro 'object'); 88 | var propertiesDecl = []; 89 | var keys = [ for (k in properties.keys()) k ]; 90 | keys.sort(sort); 91 | for (key in keys) { 92 | propertiesDecl.push({ 93 | expr:EBinop( 94 | OpArrow, 95 | macro $v{key}, 96 | _toExpr(properties.get(key), '', defaults.get(key), parsingType) 97 | ), 98 | pos:Context.currentPos() 99 | }); 100 | } 101 | var propertiesExpr = {expr: EArrayDecl(propertiesDecl), pos: Context.currentPos()}; 102 | declare("properties", propertiesExpr); 103 | if (rq.length > 0) { 104 | rq.sort(sort); 105 | var requiredExpr = {expr:EArrayDecl(rq.map(str2Expr)), pos: Context.currentPos()}; 106 | declare("required", requiredExpr); 107 | } 108 | declare("additionalProperties", macro false); 109 | case JTArray(type): 110 | declare("type", macro "array"); 111 | declare("items", toExpr(type, parsingType)); 112 | case JTMap(onlyInt, type): 113 | declare("type", macro "object"); 114 | if (onlyInt) { 115 | var patternPropertiesExpr = { 116 | expr: EArrayDecl([ 117 | {expr:EBinop(OpArrow, macro "/^[-+]?\\d+([Ee][+-]?\\d+)?$/", toExpr(type, parsingType)), pos:Context.currentPos()} 118 | ]), 119 | pos: Context.currentPos() 120 | } 121 | declare("patternProperties", patternPropertiesExpr); 122 | } 123 | else { 124 | declare("additionalProperties_obj", toExpr(type, parsingType)); 125 | } 126 | case JTRef(name): 127 | declare("ref", str2Expr('#/definitions/'+name)); 128 | case JTEnumValues(values): 129 | if (parsingType.useEnumDescriptions) { 130 | var enumDecls = []; 131 | var descrDecls = []; 132 | var descrLength = 0; 133 | var type = '_string'; 134 | 135 | function parseValue (v:JsonType) { 136 | switch (v) { 137 | case JTNull: 138 | enumDecls.push(macro null); 139 | case JTString(s): 140 | enumDecls.push(macro $v{s}); 141 | case JTFloat(s): 142 | enumDecls.push(macro $v{Std.parseFloat(s)}); 143 | type = '_float'; 144 | case JTInt(i): 145 | enumDecls.push(macro $v{i}); 146 | type = '_int'; 147 | case JTBool(b): 148 | enumDecls.push(macro $v{b}); 149 | type = '_bool'; 150 | case JTWithDescr(v, descr): 151 | descrDecls.push(macro $v{clean(descr, parsingType)}); 152 | descrLength += descr.length; 153 | parseValue(v); 154 | default: 155 | } 156 | if (enumDecls.length > descrDecls.length) { 157 | descrDecls.push(macro ''); 158 | } 159 | } 160 | 161 | for (v in values) { 162 | parseValue(v); 163 | } 164 | 165 | var enumValues = {expr: EArrayDecl(enumDecls), pos: Context.currentPos()}; 166 | declare("enum"+type, enumValues); 167 | 168 | if (descrLength > 0) { 169 | var descr = {expr: EArrayDecl(descrDecls), pos: Context.currentPos()}; 170 | var descrLabel = parsingType.useMarkdownLabel ? "markdownEnumDescriptions" : "enumDescriptions"; 171 | declare(descrLabel, descr); 172 | } 173 | } 174 | else { 175 | return _toExpr(JTAnyOf(values), descr, defaultValue, parsingType); 176 | } 177 | case JTAnyOf(types): 178 | var decls = [ for (t in types) toExpr(t, parsingType)]; 179 | var anyOfExpr = {expr: EArrayDecl(decls), pos:Context.currentPos()}; 180 | declare("anyOf", anyOfExpr); 181 | case JTWithDescr(type, descr): 182 | return _toExpr(type, descr, defaultValue, parsingType); 183 | } 184 | 185 | if (descr != '') { 186 | var description = parsingType.useMarkdownLabel ? "markdownDescription" : "description"; 187 | declare(description, str2Expr(clean(descr.trim(), parsingType))); 188 | } 189 | 190 | if (defaultValue != null) { 191 | declare('defaultValue', defaultValue); 192 | } 193 | 194 | return declsToAnonDecl(decls); 195 | } 196 | 197 | /** 198 | * Clean the doc 199 | * if all line first non whitespace char is * then remove all first * 200 | * 201 | * if parsingType.useMarkdown: 202 | * keep all new line as they are md formatting 203 | * else: 204 | * a single new line is for readability so they are removed 205 | */ 206 | static function clean (doc:String, parsingType:ParsingType) : String { 207 | var lines = []; 208 | var hasStar = doc.charAt(0) == '*'; 209 | var start = 0; 210 | var cursor = 0; 211 | while (cursor < doc.length) { 212 | switch (doc.charAt(cursor)) { 213 | case "\r": 214 | if (doc.charAt(cursor+1) == "\n") { 215 | cursor++; 216 | } 217 | var line = doc.substring(start, cursor).rtrim(); 218 | lines.push(line); 219 | start = cursor + 1; 220 | if (line.length > 0) { 221 | hasStar = hasStar && (line.charAt(0) == '*'); 222 | } 223 | case "\n": 224 | var line = doc.substring(start, cursor).rtrim(); 225 | lines.push(line); 226 | start = cursor + 1; 227 | if (line.length > 0) { 228 | hasStar = hasStar && (line.charAt(0) == '*'); 229 | } 230 | default: 231 | } 232 | cursor++; 233 | } 234 | 235 | lines.push(doc.substring(start).rtrim()); 236 | 237 | if (parsingType.useMarkdown) { 238 | if (!hasStar) { 239 | lines = keepSharedIndentation(lines); 240 | return lines.join('\n'); 241 | } 242 | else { 243 | return [ for (l in lines) l.substr(2) ].join('\n'); 244 | } 245 | } 246 | 247 | var consecutiveNewLine = 0; 248 | var result = [""]; 249 | var i = -1; 250 | for (line in lines) { 251 | line = (hasStar) ? line.substr(2) : line; 252 | line = line.ltrim(); 253 | if (line.trim().length == 0) { 254 | if (i == -1) { 255 | continue; 256 | } 257 | consecutiveNewLine++; 258 | } 259 | else { 260 | if (i == -1) { i = 0; } 261 | else { 262 | var curr = result[i]; 263 | if (curr != "" && curr.charAt(curr.length - 1) != " " && line.charAt(0) != " ") { 264 | line = " " + line; 265 | } 266 | } 267 | result[i] += line; 268 | consecutiveNewLine = 1; 269 | } 270 | if (consecutiveNewLine > 1) { 271 | result.push(''); 272 | i++; 273 | consecutiveNewLine = 0; 274 | } 275 | } 276 | return result.join("\n"); 277 | } 278 | 279 | static function keepSharedIndentation (lines:Array) : Array { 280 | var first = lines.shift().ltrim(); 281 | var newLines = []; 282 | if (first.length == 0) { 283 | while (lines.length > 0) { 284 | if (lines[0].ltrim().length == 0) { 285 | lines.shift(); 286 | } 287 | else { 288 | break; 289 | } 290 | } 291 | } 292 | else { 293 | newLines.push(first); 294 | } 295 | var i = 0; 296 | var flag = true; 297 | while (flag) { 298 | var char = null; 299 | for (line in lines) { 300 | if (line.length > i) { 301 | if (!line.isSpace(i)) { 302 | flag = false; 303 | break; 304 | } 305 | 306 | var c = line.charAt(i); 307 | if (char == null) { 308 | char = c; 309 | } 310 | else if (c != char) { 311 | flag = false; 312 | break; 313 | } 314 | flag = true; 315 | } 316 | } 317 | if (char == null) { 318 | flag = false; 319 | } 320 | else { 321 | i++; 322 | } 323 | } 324 | 325 | for (line in lines) { 326 | newLines.push(line.substring(i)); 327 | } 328 | 329 | while (newLines.length > 0) { 330 | if (newLines[newLines.length - 1].length == 0) { 331 | newLines.pop(); 332 | } 333 | else { 334 | break; 335 | } 336 | } 337 | 338 | return newLines; 339 | } 340 | #end 341 | } -------------------------------------------------------------------------------- /tests/AbstractTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2018 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package tests; 24 | 25 | import json2object.JsonParser; 26 | import json2object.JsonWriter; 27 | import utest.Assert; 28 | 29 | abstract Username (String) from String to String 30 | { 31 | public function get_id () return this.toLowerCase(); 32 | } 33 | 34 | @:forward(length) 35 | abstract Rights (Array) to Array 36 | { 37 | } 38 | 39 | @:forward(length) 40 | abstract Templated (Array) to Array 41 | { 42 | } 43 | 44 | typedef B = { 45 | t : Templated> 46 | } 47 | 48 | 49 | typedef AbstractStruct = { 50 | @:optional @:default([]) 51 | var a:ReadonlyArray; 52 | } 53 | 54 | @:forward(length, toString) 55 | abstract ReadonlyArray(Array) from Array {} 56 | 57 | @:enum 58 | abstract EnumAbstractInt(Int) { 59 | var A = 1; 60 | var B = 2; 61 | var Z = 26; 62 | } 63 | 64 | typedef EnumAbstractIntStruct = { 65 | val:EnumAbstractInt 66 | } 67 | 68 | @:enum 69 | abstract EnumAbstractString(Null) { 70 | var SA = "Z"; 71 | var SB = "Y"; 72 | var SZ = "A"; 73 | } 74 | 75 | typedef EnumAbstractStringStruct = { 76 | @optional var val:EnumAbstractString; 77 | } 78 | 79 | @:enum 80 | abstract TernaryValue(Null) { 81 | var BA = true; 82 | var BB = false; 83 | var BC = null; 84 | } 85 | 86 | typedef TernaryStruct = { 87 | var val:TernaryValue; 88 | } 89 | 90 | @:enum 91 | abstract FloatValue(Float) { 92 | var PI = 3.14; 93 | var ZERO = 0.0; 94 | } 95 | 96 | typedef FloatStruct = { 97 | var val:FloatValue; 98 | } 99 | 100 | abstract MultiFrom (String) from String to String 101 | { 102 | inline function new(i:String) { 103 | this = i; 104 | } 105 | 106 | @:from 107 | static public function fromInt(s:Int) { 108 | return new MultiFrom(Std.string(s)); 109 | } 110 | 111 | } 112 | 113 | abstract OnClass (OnClassData) 114 | { 115 | } 116 | 117 | class OnClassData 118 | { 119 | public var x:Int; 120 | } 121 | 122 | // Issue #88 123 | abstract MyMap(Map) 124 | { 125 | } 126 | 127 | // Issue #88 128 | abstract MyArray2D(Array>) 129 | { 130 | } 131 | 132 | class AbstractTest implements utest.ITest 133 | { 134 | public function new () {} 135 | 136 | public function test1 () 137 | { 138 | var parser = new JsonParser<{ username:Username }>(); 139 | var writer = new JsonWriter<{ username:Username }>(); 140 | var data = parser.fromJson('{ "username": "Administrator" }', "test"); 141 | Assert.equals("Administrator", data.username); 142 | Assert.equals("administrator", data.username.get_id()); 143 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 144 | } 145 | 146 | public function test2 () 147 | { 148 | var parser = new JsonParser<{ rights:Rights }>(); 149 | var writer = new JsonWriter<{ rights:Rights }>(); 150 | var data = parser.fromJson('{ "rights": ["Full", "Write", "Read", "None"] }', "test"); 151 | Assert.equals(4, data.rights.length); 152 | Assert.equals("Write", data.rights[1]); 153 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 154 | } 155 | 156 | public function test3 () 157 | { 158 | var parser = new JsonParser<{ t:Templated }>(); 159 | var writer = new JsonWriter<{ t:Templated }>(); 160 | var data = parser.fromJson('{ "t": [2, 1, 0] }', "test"); 161 | Assert.equals(3, data.t.length); 162 | Assert.equals(0, data.t[2]); 163 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 164 | } 165 | 166 | public function test4 () 167 | { 168 | var parser = new JsonParser(); 169 | var writer = new JsonWriter(); 170 | var data = parser.fromJson('{ "t": [[0,1], [1,0]] }', "test"); 171 | Assert.equals(2, data.t.length); 172 | Assert.equals(2, data.t[1].length); 173 | Assert.equals(1, data.t[0][1]); 174 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 175 | } 176 | 177 | public function test5 () 178 | { 179 | var parser = new JsonParser(); 180 | var writer = new JsonWriter(); 181 | var data = parser.fromJson('{}', 'test'); 182 | Assert.equals(0, data.a.length); 183 | Assert.equals(0, parser.errors.length); 184 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 185 | 186 | data = parser.fromJson('{"a":[1,1,2,3]}', 'test'); 187 | Assert.same([1,1,2,3], data.a); 188 | Assert.equals(0, parser.errors.length); 189 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 190 | } 191 | 192 | public function test6 () 193 | { 194 | var parser = new JsonParser(); 195 | var writer = new JsonWriter(); 196 | var data = parser.fromJson('"test"', 'test'); 197 | Assert.equals("test", data); 198 | Assert.equals(0, parser.errors.length); 199 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 200 | 201 | var data = parser.fromJson('555', 'test'); 202 | Assert.equals("555", data); 203 | Assert.equals(0, parser.errors.length); 204 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 205 | } 206 | 207 | public function test7 () 208 | { 209 | var parser = new JsonParser(); 210 | var writer = new JsonWriter(); 211 | var data = parser.fromJson('{"val":1}',''); 212 | Assert.equals(A, data.val); 213 | Assert.equals(0, parser.errors.length); 214 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 215 | 216 | data = parser.fromJson('{"val":26}',''); 217 | Assert.equals(Z, data.val); 218 | Assert.equals(0, parser.errors.length); 219 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 220 | 221 | data = parser.fromJson('{"val":16}',''); 222 | Assert.equals(A, data.val); 223 | Assert.equals(2, parser.errors.length); 224 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 225 | 226 | data = parser.fromJson('{"val":26.2}',''); 227 | Assert.equals(A, data.val); 228 | Assert.equals(2, parser.errors.length); 229 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 230 | } 231 | 232 | public function test8 () 233 | { 234 | var parser = new JsonParser(); 235 | var writer = new JsonWriter(); 236 | var data = parser.fromJson('{"val":"Z"}',''); 237 | Assert.equals(SA, data.val); 238 | Assert.equals(0, parser.errors.length); 239 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 240 | 241 | data = parser.fromJson('{"val":"A"}',''); 242 | Assert.equals(SZ, data.val); 243 | Assert.equals(0, parser.errors.length); 244 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 245 | 246 | data = parser.fromJson('{"val":"B"}',''); 247 | Assert.equals(SA, data.val); 248 | Assert.equals(2, parser.errors.length); 249 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 250 | 251 | data = parser.fromJson('{"val":26.2}',''); 252 | Assert.equals(SA, data.val); 253 | Assert.equals(2, parser.errors.length); 254 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 255 | } 256 | 257 | public function test9 () 258 | { 259 | var parser = new JsonParser(); 260 | var writer = new JsonWriter(); 261 | var data = parser.fromJson('{"val":true}',''); 262 | Assert.equals(BA, data.val); 263 | Assert.equals(0, parser.errors.length); 264 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 265 | 266 | data = parser.fromJson('{"val":null}',''); 267 | Assert.equals(BC, data.val); 268 | Assert.equals(0, parser.errors.length); 269 | data = parser.fromJson('{"val":"B"}',''); 270 | Assert.equals(BA, data.val); 271 | Assert.equals(2, parser.errors.length); 272 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 273 | } 274 | 275 | public function test10 () 276 | { 277 | var parser = new json2object.JsonParser(); 278 | var writer = new json2object.JsonWriter(); 279 | var data = parser.fromJson('{"val":3.14}',''); 280 | Assert.equals(PI, data.val); 281 | Assert.equals(0, parser.errors.length); 282 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 283 | 284 | data = parser.fromJson('{"val":0.0}',''); 285 | Assert.equals(ZERO, data.val); 286 | Assert.equals(0, parser.errors.length); 287 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 288 | 289 | data = parser.fromJson('{"val":1}',''); 290 | Assert.equals(PI, data.val); 291 | Assert.equals(2, parser.errors.length); 292 | Assert.same(data, parser.fromJson(writer.write(data),"test")); 293 | } 294 | 295 | public function test11 () 296 | { 297 | var parser = new json2object.JsonParser(); 298 | parser.fromJson('{"x":1}', ""); 299 | Assert.isTrue(true); // Just check that it compiles 300 | } 301 | 302 | #if !(cs || java || hl) 303 | public function test12 () 304 | { 305 | var parser = new json2object.JsonParser(); 306 | var data = parser.fromJson('{"hello":"world"}', ""); 307 | Assert.same(data.hello, "world"); 308 | 309 | //var writer = new json2object.JsonWriter(); 310 | //Assert.same(data, parser.fromJson(writer.write(data), "")); 311 | } 312 | #end 313 | 314 | #if !lua 315 | public function test13 () { 316 | var schema = new json2object.utils.JsonSchemaWriter().schema; 317 | var oracle = '{"$$schema": "http://json-schema.org/draft-07/schema#","$$ref": "#/definitions/tests.TernaryValue","definitions": {"tests.TernaryValue": {"anyOf": [{"const": true},{"const": false},{"const": null}]}}}'; 318 | Assert.isTrue(JsonComparator.areSame(oracle, schema)); 319 | } 320 | #end 321 | 322 | // Issue #88 323 | public function test14 () { 324 | var parser = new json2object.JsonParser>>(); 325 | var src = '[[[["test"]]]]'; 326 | var oracle = [[[["test"]]]]; 327 | var data = parser.fromJson(src); 328 | Assert.same(oracle, data); 329 | } 330 | 331 | // Issue #88 332 | public function test15 () { 333 | var parser = new json2object.JsonParser>(); 334 | var src = '{"1": "test"}'; 335 | var oracle = [ 1 => "test" ]; 336 | var data = parser.fromJson(src); 337 | Assert.same(oracle, data); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/json2object/utils/schema/DataBuilder.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object.utils.schema; 24 | 25 | #if !macro 26 | class DataBuilder {} 27 | #else 28 | import haxe.macro.Expr; 29 | import haxe.macro.Context; 30 | import haxe.macro.Type; 31 | import haxe.macro.TypeTools; 32 | import json2object.Error; 33 | import json2object.utils.schema.JsonType; 34 | 35 | using haxe.macro.ExprTools; 36 | using haxe.macro.TypeTools; 37 | using json2object.utils.schema.JsonTypeTools; 38 | using json2object.utils.schema.ParsingType; 39 | using StringTools; 40 | 41 | typedef Definitions = Map; 42 | 43 | class DataBuilder { 44 | 45 | static var BOOL = Context.getType("Bool"); 46 | static var FLOAT = Context.getType("Float"); 47 | static var STRING = Context.getType("String"); 48 | 49 | static var counter:Int = 0; 50 | private static var writers = new Map(); 51 | 52 | private inline static function describe (type:JsonType, descr:Null) { 53 | return (descr == null) ? type : JTWithDescr(type, descr); 54 | } 55 | 56 | private static function define(name:String, type:JsonType, definitions:Definitions, doc:Null=null) { 57 | definitions.set(name, describe(type, doc)); 58 | } 59 | 60 | static function anyOf(t1:JsonType, t2:JsonType) { 61 | return switch [t1, t2] { 62 | case [null, t], [t, null]: t; 63 | case [JTNull, JTAnyOf(v)], [JTAnyOf(v), JTNull] if (v.indexOf(JTNull) != -1): t2; 64 | case [JTAnyOf(v1), JTAnyOf(v2)]: JTAnyOf(v1.concat(v2)); 65 | case [JTAnyOf(val), t], [t, JTAnyOf(val)]: JTAnyOf(val.concat([t])); 66 | default: JTAnyOf([t1, t2]); 67 | } 68 | } 69 | 70 | private static inline function canHaveDefault(type:Type) : Bool { 71 | var t = type.followWithAbstracts(); 72 | return Context.unify(t, BOOL) || Context.unify(t, FLOAT) || Context.unify(t, STRING); 73 | } 74 | 75 | static function makeAbstractSchema(type:Type, definitions:Definitions):JsonType { 76 | var name = type.toString(); 77 | var doc:Null = null; 78 | switch (type) { 79 | case TAbstract(_.get()=>t, p): 80 | var jt:Null = null; 81 | var from = (t.from.length == 0) ? [{t:t.type, field:null}] : t.from; 82 | var i = 0; 83 | for(fromType in from) { 84 | try { 85 | var ft = fromType.t.applyTypeParameters(t.params, p); 86 | var ft = ft.followWithAbstracts(); 87 | jt = anyOf(jt, makeSchema(ft, definitions)); 88 | } 89 | catch (_:Any) {} 90 | } 91 | if (jt == null) { 92 | throw AbstractNoJsonRepresentation(name); 93 | } 94 | define(name, jt, definitions, doc); 95 | return JTRef(name); 96 | default: 97 | throw UnsupportedSchemaType(name); 98 | } 99 | } 100 | static function makeAbstractEnumSchema(type:Type, definitions:Definitions):JsonType { 101 | var name = type.toString(); 102 | var doc:Null = null; 103 | switch (type.followWithAbstracts()) { 104 | case TInst(_.get()=>t, _): 105 | if (t.module != "String") { 106 | throw UnsupportedAbstractEnumType(name); 107 | } 108 | case TAbstract(_.get()=>t, _): 109 | if (t.module != "StdTypes" && (t.name != "Int" && t.name != "Bool" && t.name != "Float")) { 110 | throw UnsupportedAbstractEnumType(name); 111 | } 112 | default: 113 | throw UnsupportedAbstractEnumType(name); 114 | } 115 | var values = new Array(); 116 | var docs = []; 117 | 118 | function handleExpr(expr:TypedExprDef, ?rec:Bool=true) : Any { 119 | return switch (expr) { 120 | case TConst(TString(s)): JTString(s); 121 | case TConst(TNull): JTString(null); 122 | case TConst(TBool(b)): JTBool(b); 123 | case TConst(TFloat(f)): JTFloat(f); 124 | case TConst(TInt(i)): JTInt(i); 125 | case TCast(c, _) if (rec): handleExpr(c.expr, false); 126 | default: throw HandleExpr; 127 | } 128 | } 129 | 130 | var enumValues = []; 131 | switch (type) { 132 | case TAbstract(_.get()=>t, p) : 133 | doc = t.doc; 134 | for (field in t.impl.get().statics.get()) { 135 | if (!field.meta.has(":enum") || !field.meta.has(":impl")) { 136 | continue; 137 | } 138 | if (field.expr() == null) { continue; } 139 | try { 140 | enumValues.push(describe(handleExpr(field.expr().expr), field.doc)); 141 | } 142 | catch (e:InternalError) { 143 | if (e != HandleExpr) { 144 | throw e; 145 | } 146 | } 147 | } 148 | default: 149 | } 150 | 151 | if (enumValues.length == 0) { 152 | throw UnsupportedEnumAbstractValue(name); 153 | } 154 | define(name, JTEnumValues(enumValues), definitions, doc); 155 | return JTRef(name); 156 | } 157 | static function makeEnumSchema(type:Type, definitions:Definitions):JsonType { 158 | var name = type.toString(); 159 | var doc:Null = null; 160 | 161 | var complexProperties = new Map(); 162 | var jt = null; 163 | switch (type) { 164 | case TEnum(_.get()=>t, p): 165 | for (n in t.names) { 166 | var construct = t.constructs.get(n); 167 | var properties = new Map(); 168 | var required = []; 169 | switch (construct.type) { 170 | case TEnum(_,_): 171 | jt = anyOf(jt, describe(JTString(n), construct.doc)); 172 | case TFun(args,_): 173 | for (a in args) { 174 | properties.set(a.name, makeSchema(a.t.applyTypeParameters(t.params, p), definitions)); 175 | if (!a.opt) { 176 | required.push(a.name); 177 | } 178 | } 179 | default: 180 | continue; 181 | } 182 | jt = anyOf(jt, JTObject([n=>describe(JTObject(properties, required, new Map()), construct.doc)], [n], new Map())); 183 | } 184 | doc = t.doc; 185 | default: 186 | } 187 | define(name, jt, definitions, doc); 188 | return JTRef(name); 189 | } 190 | 191 | static function makeMapSchema(keyType:Type, valueType:Type, definitions:Definitions):JsonType { 192 | var name = 'Map<${keyType.toString()}, ${valueType.toString()}>'; 193 | if (definitions.exists(name)) { 194 | return JTRef(name); 195 | } 196 | var onlyInt = switch (keyType) { 197 | case TInst(_.get()=>t, _): 198 | if (t.module == "String") { 199 | false; 200 | } 201 | else { 202 | throw UnsupportedMapKeyType(keyType.toString()); 203 | } 204 | case TAbstract(_.get()=>t, _): 205 | if (t.module == "StdTypes" && t.name == "Int") { 206 | true; 207 | } 208 | else { 209 | throw UnsupportedMapKeyType(keyType.toString()); 210 | } 211 | default: 212 | throw UnsupportedMapKeyType(keyType.toString()); 213 | } 214 | define(name, JTMap(onlyInt, makeSchema(valueType, definitions)), definitions); 215 | return JTRef(name); 216 | } 217 | static function makeObjectSchema(type:Type, name:String, definitions:Definitions) : JsonType { 218 | var properties = new Map(); 219 | var required = new Array(); 220 | 221 | var fields:Array; 222 | 223 | var tParams:Array; 224 | var params:Array; 225 | 226 | var doc:Null = null; 227 | 228 | switch (type) { 229 | case TAnonymous(_.get()=>t): 230 | fields = t.fields; 231 | tParams = []; 232 | params = []; 233 | 234 | case TInst(_.get()=>t, p): 235 | fields = []; 236 | var s = t; 237 | while (s != null) 238 | { 239 | fields = fields.concat(s.fields.get()); 240 | s = s.superClass != null ? s.superClass.t.get() : null; 241 | } 242 | 243 | tParams = t.params; 244 | params = p; 245 | doc = t.doc; 246 | 247 | case _: 248 | throw UnsupportedSchemaObjectType(name); 249 | } 250 | 251 | try { 252 | var defaults = new Map(); 253 | define(name, null, definitions); // Protection against recursive types 254 | for (field in fields) { 255 | if (field.meta.has(":jignored")) { continue; } 256 | switch(field.kind) { 257 | case FVar(r,w): 258 | if (r == AccCall && w == AccCall && !field.meta.has(":isVar")) { 259 | continue; 260 | } 261 | 262 | var f_type = field.type.applyTypeParameters(tParams, params); 263 | var f_name = field.name; 264 | for (m in field.meta.extract(":alias")) { 265 | if (m.params != null && m.params.length == 1) { 266 | switch (m.params[0].expr) { 267 | case EConst(CString(s)): f_name = s; 268 | default: 269 | } 270 | } 271 | } 272 | 273 | var optional = field.meta.has(":optional"); 274 | if (!optional) { 275 | required.push(f_name); 276 | } 277 | 278 | var defaultMeta = field.meta.extract(':default'); 279 | if (defaultMeta.length == 1 && canHaveDefault(f_type)) { 280 | var params = defaultMeta[0].params; 281 | if (params != null && params.length == 1) { 282 | var ct = f_type.toComplexType(); 283 | var parserCls = {name:"JsonParser", pack:["json2object"], params:[TPType(ct)]}; 284 | var writerCls = {name:"JsonWriter", pack:["json2object"], params:[TPType(ct)]}; 285 | var e = (params[0].toString() == "auto") ? macro new $parserCls([], new json2object.PositionUtils(''), NONE).getAuto() : params[0]; 286 | defaults.set(f_name, macro new $writerCls(true).write($e)); 287 | } 288 | } 289 | 290 | properties.set(f_name, describe(makeSchema(f_type, definitions, optional), field.doc)); 291 | default: 292 | } 293 | } 294 | 295 | define(name, JTObject(properties, required, defaults), definitions, doc); 296 | return JTRef(name); 297 | } 298 | catch (e:Any) { 299 | if (definitions.get(name) == null) { 300 | definitions.remove(name); 301 | } 302 | throw e; 303 | } 304 | } 305 | 306 | static function makeSchema(type:Type, definitions:Null, ?name:String=null, ?optional:Bool=false) : JsonType { 307 | 308 | if (name == null) { 309 | name = type.toString(); 310 | } 311 | 312 | if (definitions.exists(name)) { 313 | return JTRef(name); 314 | } 315 | 316 | var schema = switch (type) { 317 | case TInst(_.get()=>t, p): 318 | switch (t.module) { 319 | case "String": 320 | return JTSimple("string"); 321 | case "Array" | "List" | "haxe.ds.List" if (p.length == 1 && p[0] != null): 322 | return JTArray(makeSchema(p[0], definitions)); 323 | default: 324 | makeObjectSchema(type, name, definitions); 325 | } 326 | case TAnonymous(_): 327 | makeObjectSchema(type, name, definitions); 328 | case TAbstract(_.get()=>t, p): 329 | if (t.name == "Null") { 330 | var jt = makeSchema(p[0], definitions); 331 | return (optional) ? jt : anyOf(JTNull, jt); 332 | } 333 | else if (t.module == "StdTypes") { 334 | switch (t.name) { 335 | case "Int": return JTSimple("integer"); 336 | case "Float", "Single": JTSimple("number"); 337 | case "Bool": return JTSimple("boolean"); 338 | default: throw CannotGenerateSchema(t.name); 339 | } 340 | } 341 | else if (t.module == #if (haxe_ver >= 4) "haxe.ds.Map" #else "Map" #end) { 342 | makeMapSchema(p[0], p[1], definitions); 343 | } 344 | else { 345 | if (t.meta.has(":enum")) { 346 | makeAbstractEnumSchema(type.applyTypeParameters(t.params, p), definitions); 347 | } 348 | else { 349 | makeAbstractSchema(type.applyTypeParameters(t.params, p), definitions); 350 | } 351 | } 352 | case TEnum(_.get()=>t,p): 353 | makeEnumSchema(type.applyTypeParameters(t.params, p), definitions); 354 | case TType(_.get()=>t, p): 355 | var _tmp = makeSchema(t.type.applyTypeParameters(t.params, p), definitions, name); 356 | if (t.name != "Null" && t.name != "List") { 357 | if (t.doc != null) { 358 | define(name, describe(definitions.get(name), t.doc), definitions); 359 | } 360 | else { 361 | define(name, definitions.get(name), definitions); 362 | } 363 | } 364 | (t.name == "Null" && !optional) ? anyOf(JTNull, _tmp) : _tmp; 365 | case TLazy(f): 366 | makeSchema(f(), definitions); 367 | default: 368 | throw UnsupportedSchemaType(name); 369 | } 370 | return schema; 371 | } 372 | 373 | static function format(schema:JsonType, definitions:Definitions, parsingType:ParsingType) : Expr { 374 | inline function finishDecl (decl:{field:String, expr:Expr}) #if haxe4 : ObjectField #end { 375 | #if haxe4 376 | return {field: decl.field, expr: decl.expr, quotes:Quoted}; 377 | #else 378 | return decl; 379 | #end 380 | } 381 | 382 | var decls = []; 383 | var schemaExpr:Expr = macro $v{"http://json-schema.org/draft-07/schema#"}; 384 | decls.push(finishDecl({field:"schema", expr:schemaExpr})); 385 | var hasDef = definitions.keys().hasNext(); 386 | if (hasDef) { 387 | var definitionsExpr = { 388 | expr: EArrayDecl( 389 | [ for (key in definitions.keys()) 390 | { 391 | expr: EBinop( 392 | OpArrow, 393 | macro $v{key}, 394 | definitions.get(key).toExpr(parsingType) 395 | ), 396 | pos: Context.currentPos() 397 | } 398 | ] 399 | ), 400 | pos: Context.currentPos() 401 | }; 402 | decls.push(finishDecl({field: 'definitions', expr: definitionsExpr})); 403 | } 404 | 405 | switch (schema.toExpr(parsingType).expr) { 406 | case EObjectDecl(fields): 407 | decls = decls.concat(fields); 408 | default: 409 | } 410 | 411 | return { expr: EObjectDecl(decls), pos: Context.currentPos()} ; 412 | 413 | } 414 | 415 | static function makeSchemaWriter(c:BaseType, type:Type, parsingType:ParsingType) { 416 | var swriterName = c.name + "_" + (counter++); 417 | 418 | var definitions = new Definitions(); 419 | var obj = format(makeSchema(type, definitions), definitions, parsingType); 420 | var schemaWriter = macro class $swriterName { 421 | public var space:String; 422 | public function new (space:String='') { 423 | this.space = space; 424 | } 425 | 426 | private var _schema : Null; 427 | public var schema(get,never):String; 428 | function get_schema () : String { 429 | if (_schema == null) { 430 | @:privateAccess { 431 | _schema = new json2object.JsonWriter(true)._write(${obj}, space, 0, true, function () { return '"const": null'; }); 432 | } 433 | } 434 | return _schema; 435 | } 436 | } 437 | haxe.macro.Context.defineType(schemaWriter); 438 | 439 | var constructedType = haxe.macro.Context.getType(swriterName); 440 | return haxe.macro.Context.getType(swriterName); 441 | } 442 | 443 | public static function build() { 444 | switch (Context.getLocalType()) { 445 | case TInst(_.get()=>c, [type]): 446 | var parsingType:ParsingType = switch (c.name) { 447 | case "VSCodeSchemaWriter": { useEnumDescriptions: true, useMarkdown: true, useMarkdownLabel: true }; 448 | default: { useEnumDescriptions: false, useMarkdown: false, useMarkdownLabel: false }; 449 | } 450 | return makeSchemaWriter(c, type, parsingType); 451 | case _: 452 | Context.fatalError("json2object: Json schema tools must be a class", Context.currentPos()); 453 | return null; 454 | } 455 | } 456 | } 457 | #end 458 | -------------------------------------------------------------------------------- /src/json2object/writer/DataBuilder.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | package json2object.writer; 23 | 24 | #if !macro 25 | class DataBuilder {} 26 | #else 27 | import haxe.macro.Expr; 28 | import haxe.macro.Context; 29 | import haxe.macro.Type; 30 | import haxe.macro.TypeTools; 31 | import json2object.Error; 32 | 33 | using StringTools; 34 | using haxe.macro.ExprTools; 35 | using json2object.utils.TypeTools; 36 | 37 | class DataBuilder { 38 | @:persistent 39 | private static var counter = 0; 40 | private static var writers = new Map(); 41 | private static var jcustom = ":jcustomwrite"; 42 | 43 | private static function notNull (type:Type) : Type { 44 | return switch (type) { 45 | case TAbstract(_.get()=>t, p): 46 | (t.name == "Null") ? notNull(p[0]) : type; 47 | case TType(_.get()=>t, p): 48 | (t.name == "Null") ? notNull(type.follow()) : type; 49 | default: 50 | type; 51 | } 52 | } 53 | 54 | private static function isNullable (type:Type) : Bool { 55 | if (notNull(type) != type) { return true; } 56 | return switch (type.followWithAbstracts()) { 57 | case TAbstract(_.get()=>t,_): 58 | !t.meta.has(":notNull"); 59 | default: 60 | true; 61 | } 62 | } 63 | 64 | private static function makeStringWriter () : Expr { 65 | return macro return ((indentFirst) ? buildIndent(space, level) : '') + ((o == null) ? "null" : quote(cast o)); 66 | } 67 | 68 | private static function makeBasicWriter (type:Type) : Expr { 69 | return isNullable(type) 70 | ? macro return ((indentFirst) ? buildIndent(space, level) : '') + ((o == null) ? "null" : o+"") 71 | : macro return ((indentFirst) ? buildIndent(space, level) : '') + o; 72 | } 73 | 74 | private static function makeArrayWriter (subType:Type, baseParser:BaseType) : Expr { 75 | var cls = { name:baseParser.name, pack:baseParser.pack, params:[TPType(subType.toComplexType())]}; 76 | return macro { 77 | var indent = buildIndent(space, level); 78 | var firstIndent = (indentFirst) ? indent : ''; 79 | if (o == null) { return firstIndent + "null"; } 80 | var valueWriter = new $cls(ignoreNullOptionals); 81 | 82 | @:privateAccess { 83 | var values = [for (element in o) valueWriter._write(element, space, level + 1, true, onAllOptionalNull)]; 84 | var newLine = (space != '' && o.length > 0) ? '\n' : ''; 85 | 86 | var json = firstIndent + "[" + newLine; 87 | json += values.join(',' + newLine) + newLine; 88 | json += indent + "]"; 89 | return json; 90 | } 91 | }; 92 | } 93 | 94 | private static function makeMapWriter (keyType:Type, valueType:Type, baseParser:BaseType) : Expr { 95 | var clsValue = { name:baseParser.name, pack:baseParser.pack, params:[TPType(valueType.toComplexType())]}; 96 | 97 | var keyMacro = switch (keyType.followWithAbstracts()) { 98 | case TInst(_.get()=>t, _): 99 | if (t.module == "String") { 100 | macro quote(key); 101 | } 102 | else { 103 | Context.fatalError("json2object: Only maps with Int or String keys are writable, got "+keyType.toString(), Context.currentPos()); 104 | } 105 | case TAbstract(_.get()=>t, _): 106 | if (t.module == "StdTypes" && t.name == "Int") { 107 | macro key; 108 | } 109 | else { 110 | Context.fatalError("json2object: Only maps with Int or String keys are writable, got "+keyType.toString(), Context.currentPos()); 111 | } 112 | default: Context.fatalError("json2object: Only maps with Int or String keys are writable, got "+keyType.toString(), Context.currentPos()); 113 | } 114 | 115 | return macro { 116 | var indent = buildIndent(space, level); 117 | var firstIndent = (indentFirst) ? indent : ''; 118 | if (o == null) { return firstIndent + "null"; } 119 | var valueWriter = new $clsValue(ignoreNullOptionals); 120 | 121 | @:privateAccess { 122 | var values = [for (key in o.keys()) indent + space + '"'+key+'": '+valueWriter._write(o.get(key), space, level + 1, false, onAllOptionalNull)]; 123 | var newLine = (space != '' && values.length > 0) ? '\n' : ''; 124 | 125 | var json = firstIndent+'{' + newLine; 126 | json += values.join(',' + newLine) + newLine; 127 | json += indent+'}'; 128 | return json; 129 | } 130 | }; 131 | } 132 | 133 | private static function makeObjectOrAnonWriter (type:Type, baseParser:BaseType) : Expr { 134 | var fields:Array; 135 | 136 | var tParams:Array; 137 | var params:Array; 138 | 139 | switch (type) { 140 | case TAnonymous(_.get()=>t): 141 | fields = t.fields; 142 | tParams = []; 143 | params = []; 144 | 145 | case TInst(_.get()=>t, p): 146 | if (t.isPrivate) 147 | { 148 | t = TypeUtils.copyType(t); 149 | } 150 | 151 | fields = []; 152 | var s = t; 153 | while (s != null) 154 | { 155 | fields = fields.concat(s.fields.get()); 156 | s = s.superClass != null ? s.superClass.t.get() : null; 157 | } 158 | 159 | tParams = t.params; 160 | params = p; 161 | 162 | case _: return macro return null; 163 | } 164 | 165 | var assignations:Array = []; 166 | var skips: Array = []; 167 | 168 | for (field in fields) { 169 | if (field.meta.has(":jignored")) { continue; } 170 | switch(field.kind) { 171 | case FVar(r,w): 172 | if (r == AccCall && w == AccCall && !field.meta.has(":isVar")) { 173 | continue; 174 | } 175 | 176 | var f_a = (r == AccCall || r == AccNever || r == AccNo) ? macro Reflect.field(o, $v{field.name}) : { expr: EField(macro o, field.name), pos: Context.currentPos() }; 177 | var f_type = field.type.applyTypeParameters(tParams, params); 178 | var f_cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(f_type.toComplexType())]}; 179 | 180 | var name = field.name; 181 | for (m in field.meta.get()) { 182 | if (m.name == ":alias" && m.params.length == 1) { 183 | switch (m.params[0].expr) { 184 | case EConst(CString(s)): 185 | name = s; 186 | default: 187 | } 188 | } 189 | } 190 | name = '"' + name + '": '; 191 | 192 | var assignation:Expr = macro indent + space + $v{name}; 193 | 194 | var writer:Expr; 195 | if (field.meta.has(jcustom)) { 196 | try { 197 | writer = field.meta.extract(jcustom)[0].params[0]; 198 | validateCustomWriter(field.type, writer); 199 | } catch (e:CustomFunctionError) { 200 | Context.fatalError(invalidWriterErrorMessage(field.type, writer, e.message), Context.currentPos()); 201 | } 202 | } 203 | if (writer != null) { 204 | assignation = macro $assignation + $writer(cast $f_a); 205 | } else if (field.meta.has(":noquoting")) { 206 | assignation = macro $assignation 207 | + new $f_cls(ignoreNullOptionals).dontQuote()._write(cast $f_a, space, level + 1, false, onAllOptionalNull); 208 | } else { 209 | assignation = macro $assignation + new $f_cls(ignoreNullOptionals)._write(cast $f_a, space, level + 1, false, onAllOptionalNull); 210 | } 211 | assignations.push(assignation); 212 | 213 | if (field.meta.has(':optional')) { 214 | switch (field.type) { 215 | case TAbstract(t, params): 216 | if (t.toString() == "Null") { 217 | // Null 218 | skips.push(macro $f_a == null); 219 | } else { 220 | // Bool 221 | skips.push(macro false); 222 | } 223 | default: 224 | skips.push(macro $f_a == null); 225 | } 226 | } else { 227 | skips.push(macro false); 228 | } 229 | 230 | default: 231 | } 232 | } 233 | var array = {expr:EArrayDecl(assignations), pos:Context.currentPos()}; 234 | var skips = {expr:EArrayDecl(skips), pos:Context.currentPos()}; 235 | 236 | return macro { 237 | var indent = buildIndent(space, level); 238 | var firstIndent = (indentFirst) ? indent : ''; 239 | if (o == null) { return firstIndent + "null"; } 240 | @:privateAccess{ 241 | var decl = ${array}; 242 | if (ignoreNullOptionals) { 243 | var skips = ${skips}; 244 | if (skips.indexOf(false) == -1) { 245 | decl = onAllOptionalNull != null ? [onAllOptionalNull()] : []; 246 | } 247 | else { 248 | decl = [ for (i in 0...decl.length) skips[i] ? continue : decl[i]]; 249 | } 250 | } 251 | var newLine = (space != '' && decl.length > 0) ? '\n' : ''; 252 | 253 | var json = firstIndent + "{" + newLine; 254 | json += decl.join(',' + newLine) + newLine; 255 | json += indent + "}"; 256 | return json; 257 | } 258 | }; 259 | } 260 | 261 | private static function makeEnumWriter (type:Type, baseParser:BaseType) : Expr { 262 | var tParams:Array; 263 | var params:Array; 264 | 265 | var cases = []; 266 | switch (type) { 267 | case TEnum(_.get()=>t, p): 268 | tParams = t.params; 269 | params = p; 270 | for (n in t.names) { 271 | switch (t.constructs.get(n).type) { 272 | case TEnum(_,_): 273 | var value = '"'+n+'"'; 274 | cases.push({expr: macro firstIndent + $v{value}, guard: null, values: [macro $i{n}]}); 275 | case TFun(args, _): 276 | var constructor = []; 277 | var assignations:Array = []; 278 | for (a in args) { 279 | constructor.push(macro $i{a.name}); 280 | 281 | var a_type = a.t.applyTypeParameters(tParams, params); 282 | var a_cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(a_type.toComplexType())]}; 283 | 284 | assignations.push(macro indent + space + space + '"'+$v{a.name} +'": '+ new $a_cls(ignoreNullOptionals)._write($i{a.name}, space, level + 2, false, onAllOptionalNull)); 285 | } 286 | 287 | 288 | var call = {expr:ECall(macro $i{n}, constructor), pos:Context.currentPos()}; 289 | var array = {expr:EArrayDecl(assignations), pos:Context.currentPos()}; 290 | var jsonExpr = macro { 291 | var decl = ${array}; 292 | var newLine = (space != '' && decl.length > 0) ? '\n' : ''; 293 | var json = firstIndent +'{' + newLine; 294 | json += indent + space + '"'+$v{n}+'": {' + newLine; 295 | json += decl.join(',' + newLine) + newLine; 296 | json += indent + space +'}' + newLine; 297 | json += indent +'}'; 298 | } 299 | cases.push({expr: jsonExpr, guard: null, values: [call]}); 300 | 301 | default: 302 | } 303 | } 304 | default: 305 | } 306 | var switchExpr = {expr:ESwitch(macro o, cases, null), pos:Context.currentPos()}; 307 | return macro { 308 | var indent = buildIndent(space, level); 309 | var firstIndent = (indentFirst) ? indent : ''; 310 | if (o == null) { return firstIndent + "null"; } 311 | @:privateAccess { 312 | return $switchExpr; 313 | } 314 | }; 315 | } 316 | 317 | private static function makeAbstractEnumWriter (type:Type) : Expr { 318 | switch (type.followWithAbstracts()) { 319 | case TInst(_.get()=>t, _): 320 | if (t.module != "String") { 321 | Context.fatalError("json2object: Unsupported abstract enum type:"+type.toString(), Context.currentPos()); 322 | } 323 | else { 324 | return makeStringWriter(); 325 | } 326 | case TAbstract(_.get()=>t, _): 327 | if (t.module != "StdTypes" && (t.name != "Int" && t.name != "Bool" && t.name != "Float")) { 328 | Context.fatalError("json2object: Unsupported abstract enum type:"+type.toString(), Context.currentPos()); 329 | } 330 | else { 331 | return makeBasicWriter(type); 332 | } 333 | default: Context.fatalError("json2object: Unsupported abstract enum type:"+type.toString(), Context.currentPos()); 334 | } 335 | return null; 336 | } 337 | 338 | private static function makeCustomWriter(t:Type, c:ClassType):Expr { 339 | var cexpr:Expr; 340 | try { 341 | cexpr = c.meta.extract(jcustom)[0].params[0]; 342 | validateCustomWriter(t, cexpr); 343 | } catch (e:CustomFunctionError) { 344 | Context.fatalError(invalidWriterErrorMessage(t, cexpr, e.message), Context.currentPos()); 345 | } 346 | return macro { 347 | return ${cexpr}(o); 348 | }; 349 | } 350 | 351 | private static function invalidWriterErrorMessage(t:Type, e:Expr, m:String):String { 352 | var methodName = jcustom; 353 | 354 | if (e != null) { 355 | methodName = e.toString(); 356 | var index = methodName.lastIndexOf(".") + 1; 357 | methodName = methodName.substr(index); 358 | } 359 | 360 | return 'Failed to create custom writer using ${e.toString()}, the function prototype should be (${t.toString()})->String: $m'; 361 | } 362 | 363 | private static function validateCustomWriter(target:Type, e:Expr) { 364 | switch Context.typeof(e) { 365 | case TFun(args, ret): 366 | if (ret.toString() != "String"){ 367 | throw new CustomFunctionError('Return type should be String'); 368 | } 369 | 370 | if (args.length != 1) { 371 | throw new CustomFunctionError("Should have one argument"); 372 | } 373 | 374 | if (args[0].t.toString() != target.toString()) { 375 | throw new CustomFunctionError('Argument type should be ${target.toString()}'); 376 | } 377 | 378 | default: 379 | throw new CustomFunctionError("Custom writer should point to a static function"); 380 | } 381 | } 382 | 383 | public static function makeWriter (c:BaseType, type:Type, base:Type) { 384 | if (base == null) { base = type; } 385 | 386 | var writerMapName = base.toString(); 387 | if (writers.exists(writerMapName)) { 388 | return writers.get(writerMapName); 389 | } 390 | 391 | var writerName = c.name + "_" + (counter++); 392 | var writerClass = macro class $writerName { 393 | public var ignoreNullOptionals : Bool; 394 | private var shouldQuote : Bool = true; 395 | public function new (?ignoreNullOptionals:Bool=false) { 396 | this.ignoreNullOptionals = ignoreNullOptionals; 397 | } 398 | 399 | 400 | private inline function quote (str:String) { 401 | return shouldQuote ? json2object.writer.StringUtils.quote(str) : str; 402 | } 403 | private inline function dontQuote () { 404 | shouldQuote = false; 405 | return this; 406 | } 407 | 408 | private function buildIndent (space:String, level:Int) { 409 | if (level == 0) { return ''; } 410 | var buff = new StringBuf(); 411 | for (i in 0...level) { 412 | buff.add(space); 413 | } 414 | return buff.toString(); 415 | } 416 | }; 417 | 418 | var writeExpr = switch (type) { 419 | case TInst(_.get()=>t, p) : 420 | switch(t.module) { 421 | case "String": 422 | makeStringWriter(); 423 | case "Array" | "List" | "haxe.ds.List": 424 | if (p.length == 1 && p[0] != null) { 425 | makeArrayWriter(p[0], c); 426 | } 427 | else { 428 | macro return null; 429 | } 430 | case _: 431 | switch (t.kind) { 432 | case KTypeParameter(_): 433 | Context.fatalError("json2object: Type parameters are not writable: " + t.name, Context.currentPos()); 434 | 435 | default: 436 | macro return null; 437 | } 438 | if (t.meta.has(jcustom)) { 439 | makeCustomWriter(type, t); 440 | } else { 441 | makeObjectOrAnonWriter(type, c); 442 | } 443 | } 444 | case TAnonymous(_.get()=>t): 445 | makeObjectOrAnonWriter(type, c); 446 | case TAbstract(_.get()=>t, p): 447 | if (t.name == "Null") { 448 | return makeWriter(c, p[0], type); 449 | } 450 | else if (t.name == "Any") { 451 | Context.fatalError("json2object: Parser of "+t.name+" are not generated", Context.currentPos()); 452 | } 453 | else if (t.module == "UInt" || t.name == "UInt") { 454 | makeBasicWriter(base); 455 | } 456 | else if (t.module == "StdTypes") { 457 | switch (t.name) { 458 | case "Int", "Float", "Single", "Bool": 459 | makeBasicWriter(base); 460 | default: Context.fatalError("json2object: Parser of "+t.name+" are not generated", Context.currentPos()); 461 | } 462 | } 463 | else if (t.module == #if (haxe_ver >= 4) "haxe.ds.Map" #else "Map" #end) { 464 | makeMapWriter(p[0], p[1], c); 465 | } 466 | else { 467 | if (t.meta.has(":enum")) { 468 | makeAbstractEnumWriter(type.applyTypeParameters(t.params, p)); 469 | } 470 | else if (t.meta.has(":coreType")) { 471 | Context.fatalError("json2object: Parser of coreType ("+t.name+") are not generated", Context.currentPos()); 472 | } 473 | else { 474 | var ap = t.type.applyTypeParameters(t.params, p); 475 | return makeWriter(c, ap, ap); 476 | } 477 | } 478 | case TEnum(_.get()=>t, p): 479 | makeEnumWriter(type.applyTypeParameters(t.params, p), c); 480 | case TType(_.get()=>t, p) : 481 | return makeWriter(c, t.type.applyTypeParameters(t.params, p), type); 482 | case TLazy(f): 483 | return makeWriter(c, f(), f()); 484 | default: Context.fatalError("json2object: Writer for "+type.toString()+" are not generated", Context.currentPos()); 485 | } 486 | 487 | var onAllOptionalNullCT : ComplexType = TFunction([],Context.getType("String").toComplexType()); 488 | var args = [ 489 | {name:"o", meta:null, opt:false, type:base.toComplexType(),value:null}, 490 | {name:"space", meta:null, opt:false, type:Context.getType("String").toComplexType(), value:macro ""}, 491 | {name:"level", meta:null, opt:false, type:Context.getType("Int").toComplexType(), value:macro 0}, 492 | {name:"indentFirst", meta:null, opt:false, type:Context.getType("Bool").toComplexType(), value:macro false}, 493 | {name:"onAllOptionalNull", meta:null, opt:true, type:onAllOptionalNullCT, value: macro null} 494 | ]; 495 | var privateWrite:Field = { 496 | doc: null, 497 | kind: FFun({args:args, expr:writeExpr, params:null, ret:null}), 498 | access: [APrivate], 499 | name: "_write", 500 | pos:Context.currentPos(), 501 | meta: null 502 | } 503 | writerClass.fields.push(privateWrite); 504 | 505 | var write:Field = { 506 | doc: null, 507 | kind: FFun({args:[args[0], args[1]], expr:macro return _write(o, space, 0, false), params:null, ret:null}), 508 | access: [APublic], 509 | name: "write", 510 | pos:Context.currentPos(), 511 | meta: null 512 | } 513 | writerClass.fields.push(write); 514 | 515 | haxe.macro.Context.defineType(writerClass); 516 | 517 | var constructedType = haxe.macro.Context.getType(writerName); 518 | writers.set(writerMapName, constructedType); 519 | return constructedType; 520 | 521 | } 522 | 523 | public static function build() { 524 | switch (Context.getLocalType()) { 525 | case TInst(c, [type]): 526 | return makeWriter(c.get(), type, type); 527 | case _: 528 | Context.fatalError("json2object: Writing tools must be a class", Context.currentPos()); 529 | return null; 530 | } 531 | } 532 | } 533 | #end 534 | -------------------------------------------------------------------------------- /src/json2object/reader/DataBuilder.hx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017-2019 Guillaume Desquesnes, Valentin Lemière 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | package json2object.reader; 24 | 25 | #if !macro 26 | class DataBuilder {} 27 | #else 28 | import haxe.macro.Expr; 29 | import haxe.macro.Context; 30 | import haxe.macro.Type; 31 | import haxe.macro.TypeTools; 32 | import json2object.Error; 33 | 34 | using StringTools; 35 | using haxe.macro.ExprTools; 36 | using json2object.utils.TypeTools; 37 | 38 | typedef JsonType = {jtype:String, name:String, params:Array} 39 | typedef ParserInfo = {packs:Array, clsName:String} 40 | 41 | class DataBuilder { 42 | 43 | @:persistent 44 | private static var counter = 0; 45 | private static var parsers = new Map(); 46 | private static var callPosition:Null = null; 47 | private static var jcustom = ":jcustomparse"; 48 | 49 | private static function notNull(type:Type):Type { 50 | return switch (type) { 51 | case TAbstract(_.get()=>t, p): 52 | (t.name == "Null") ? notNull(p[0]) : type; 53 | case TType(_.get()=>t, p): 54 | (t.name == "Null") ? notNull(type.follow()) : type; 55 | default: 56 | type; 57 | } 58 | } 59 | 60 | private static function isNullable(type:Type) { 61 | if (notNull(type) != type) { return true; } 62 | return switch (type.followWithAbstracts()) { 63 | case TAbstract(_.get()=>t,_): 64 | !t.meta.has(":notNull"); 65 | default: 66 | true; 67 | } 68 | } 69 | 70 | // return true if type.followWithAbstract == String, Int, Float or Bool or Array of the previous 71 | private static function isBaseType(type:Type) { 72 | return switch (type.followWithAbstracts()) { 73 | case TInst(_.get()=>t, p): 74 | (t.module == "String" || (t.module == "Array" && isBaseType(p[0]))); 75 | case TAbstract(_.get()=>t, p): 76 | (t.module == "StdTypes" && (t.name == "Int" || t.name == "Float" || t.name == "Bool")); 77 | default: false; 78 | } 79 | } 80 | 81 | private static function changeExprOf(field:Field, e:Expr) { 82 | switch (field.kind) { 83 | case FFun(f): 84 | f.expr = e; 85 | default: return; 86 | } 87 | } 88 | 89 | private static function changeFunction(name:String, of:TypeDefinition, to:Expr) { 90 | for (field in of.fields) { 91 | if (field.name == name) { 92 | changeExprOf(field, to); 93 | } 94 | } 95 | } 96 | 97 | public static function makeStringParser(parser:TypeDefinition) { 98 | changeFunction("loadJsonString", parser, macro {value = cast s;}); 99 | changeFunction("loadJsonNull", parser, macro {value = null;}); 100 | } 101 | 102 | public static function makeIntParser(parser:TypeDefinition, ?base:Type=null) { 103 | var e = macro { 104 | value = loadJsonInt(f, pos, variable, value); 105 | }; 106 | changeFunction("loadJsonNumber", parser, e); 107 | if (base != null && isNullable(base)) { 108 | changeFunction("loadJsonNull", parser, macro {value = null;}); 109 | } 110 | } 111 | 112 | public static function makeUIntParser(parser:TypeDefinition, ?base:Type=null) { 113 | var e = macro { 114 | value = loadJsonUInt(f, pos, variable, value); 115 | }; 116 | changeFunction("loadJsonNumber", parser, e); 117 | if (base != null && isNullable(base)) { 118 | changeFunction("loadJsonNull", parser, macro {value = null;}); 119 | } 120 | } 121 | 122 | public static function makeFloatParser(parser:TypeDefinition, ?base:Type=null) { 123 | var e = macro { 124 | value = loadJsonFloat(f, pos, variable, value); 125 | }; 126 | changeFunction("loadJsonNumber", parser, e); 127 | if (base != null && isNullable(base)) { 128 | changeFunction("loadJsonNull", parser, macro {value = null;}); 129 | } 130 | } 131 | 132 | public static function makeBoolParser(parser:TypeDefinition, ?base:Type=null) { 133 | changeFunction("loadJsonBool", parser, macro { value = cast b; }); 134 | if (base != null && isNullable(base)) { 135 | changeFunction("loadJsonNull", parser, macro {value = null;}); 136 | } 137 | } 138 | 139 | public static function makeArrayParser(parser:TypeDefinition, subType:Type, baseParser:BaseType) { 140 | var cls = { name:baseParser.name, pack:baseParser.pack, params:[TPType(subType.toComplexType())]}; 141 | 142 | var e = if (Context.defined("cs")) { 143 | // Reduced version doesn't work on C#, using manual copy of the loop 144 | macro value = { 145 | var parser = new $cls(errors, putils, THROW); 146 | cast [ 147 | for (j in a) 148 | try { parser.loadJson(j, variable); } 149 | catch (e:json2object.Error.InternalError) { 150 | if (e != ParsingThrow) { 151 | throw e; 152 | } 153 | 154 | continue; 155 | } 156 | ]; 157 | } 158 | } else { 159 | macro value = cast loadJsonArrayValue(a, new $cls(errors, putils, THROW).loadJson, variable); 160 | } 161 | 162 | changeFunction("loadJsonArray", parser, e); 163 | changeFunction("loadJsonNull", parser, macro {value = null;}); 164 | } 165 | 166 | public static function makeListParser(parser:TypeDefinition, subType:Type, baseParser:BaseType) { 167 | var cls = { name:baseParser.name, pack:baseParser.pack, params:[TPType(subType.toComplexType())]}; 168 | var list = {name:"List", pack:[#if (haxe_ver >= 4)"haxe", "ds"#end], params:[TPType(subType.toComplexType())]}; 169 | 170 | var e = macro value = { 171 | var parser = new $cls(errors, putils, THROW); 172 | var res = new $list(); 173 | for (j in a) { 174 | try { 175 | res.add(parser.loadJson(j, variable)); 176 | } 177 | catch (e:json2object.Error.InternalError) { 178 | if (e != ParsingThrow) { 179 | throw e; 180 | } 181 | } 182 | } 183 | res; 184 | } 185 | 186 | changeFunction("loadJsonArray", parser, e); 187 | changeFunction("loadJsonNull", parser, macro {value = null;}); 188 | } 189 | 190 | public static function makeCustomParser(parser:TypeDefinition, type:Type, t:ClassType){ 191 | var cexpr:Expr; 192 | try { 193 | cexpr = t.meta.extract(jcustom)[0].params[0]; 194 | validateCustomParser(type, cexpr); 195 | } catch (e:CustomFunctionError) { 196 | Context.fatalError(invalidParserErrorMessage(type, cexpr, e.message), Context.currentPos()); 197 | } 198 | 199 | var e = macro { 200 | return value = ${cexpr}(json, variable); 201 | } 202 | 203 | var args:Array = [ 204 | { 205 | name: "json", 206 | type: macro:hxjsonast.Json 207 | }, 208 | { 209 | name:"variable", 210 | type: macro:String, 211 | opt: true, 212 | value: macro "" 213 | } 214 | ]; 215 | 216 | var loadJ:Field = { 217 | doc: null, 218 | kind: FFun({ 219 | args: args, 220 | expr: e, 221 | params: null, 222 | ret: TypeTools.toComplexType(type) 223 | }), 224 | access: [AOverride, APublic], 225 | name: "loadJson", 226 | pos: Context.currentPos(), 227 | meta: null 228 | } 229 | parser.fields.push(loadJ); 230 | } 231 | 232 | private static function invalidParserErrorMessage(t:Type, e:Expr, m:String):String { 233 | var methodName = jcustom; 234 | 235 | if (e != null) { 236 | methodName = e.toString(); 237 | var index = methodName.lastIndexOf(".") + 1; 238 | methodName = methodName.substr(index); 239 | } 240 | 241 | return 'Failed to create custom parser using ${e.toString()}, the function prototype should be (hxjsonast.Json, String)->${t.toString()}: $m'; 242 | } 243 | 244 | private static function validateCustomParser(target:Type, e:Expr) { 245 | switch Context.typeof(e) { 246 | case TFun(args, ret): 247 | if (ret.toString() != target.toString()){ 248 | throw new CustomFunctionError('Return type should be ${target.toString()}'); 249 | } 250 | 251 | if (args.length != 2) { 252 | throw new CustomFunctionError("Should have two arguments"); 253 | } 254 | 255 | if (args[0].t.toString() != "hxjsonast.Json") { 256 | throw new CustomFunctionError('First argument type should be hxjsonast.Json'); 257 | } 258 | 259 | if (args[1].t.toString() != "String") { 260 | throw new CustomFunctionError('Second argument type should be String'); 261 | } 262 | 263 | default: 264 | throw new CustomFunctionError("Custom parser should point to a static function"); 265 | } 266 | } 267 | 268 | public static function makeObjectOrAnonParser(parser:TypeDefinition, type:Type, superType:Type, baseParser:BaseType) { 269 | var cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(type.toComplexType())]}; 270 | 271 | var initializator:Expr; 272 | var isAnon = false; 273 | var isPrivate = null; 274 | var fields:Array; 275 | 276 | var tParams:Array; 277 | var params:Array; 278 | 279 | switch (type) { 280 | case TAnonymous(_.get()=>t): 281 | isAnon = true; 282 | fields = t.fields; 283 | tParams = []; 284 | params = []; 285 | 286 | case TInst(_.get()=>t, p): 287 | if (t.isPrivate) 288 | { 289 | t = TypeUtils.copyType(t); 290 | isPrivate = t; 291 | } 292 | 293 | fields = []; 294 | var s = t; 295 | while (s != null) 296 | { 297 | fields = fields.concat(s.fields.get()); 298 | s = s.superClass != null ? s.superClass.t.get() : null; 299 | } 300 | 301 | tParams = t.params; 302 | params = p; 303 | 304 | var pack = t.module.split("."); 305 | pack.push(t.name); 306 | 307 | var e = { 308 | expr: EConst(CIdent(pack.shift())), 309 | pos: Context.currentPos() 310 | }; 311 | 312 | while (pack.length > 0) 313 | { 314 | e = { 315 | expr: EField(e, pack.shift()), 316 | pos: Context.currentPos() 317 | }; 318 | } 319 | 320 | initializator = macro Type.createEmptyInstance($e{e}); 321 | 322 | case _: return; 323 | } 324 | 325 | var baseValues:Array<{field:String, expr:Expr #if (haxe_ver >= 4) , quotes:haxe.macro.Expr.QuoteStatus #end}> = []; 326 | var autoExprs:Array = []; 327 | var cases:Array = []; 328 | var assignedKeys:Array = []; 329 | var assignedValues:Array = []; 330 | 331 | for (field in fields) { 332 | if (field.meta.has(":jignored")) { continue; } 333 | switch(field.kind) { 334 | case FVar(r,w): 335 | if (r == AccCall && w == AccCall && !field.meta.has(":isVar")) { 336 | continue; 337 | } 338 | 339 | var needReflect = w == AccNever || w == AccCall #if (haxe_ver >= 4) || w == AccCtor #end; 340 | var canRead = r == AccNormal || r == AccNo; 341 | 342 | var f_a = { expr: EField(macro value, field.name), pos: Context.currentPos() }; 343 | var f_type = field.type.applyTypeParameters(tParams, params); 344 | var f_cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(f_type.toComplexType())]}; 345 | var nullCheck = Context.defined("cpp") && isNullable(f_type); // For cpp 346 | 347 | assignedKeys.push(macro $v{field.name}); 348 | assignedValues.push(macro $v{field.meta.has(":optional")}); 349 | 350 | var reader:Expr = macro new $f_cls(errors, putils, OBJECTTHROW).loadJson; 351 | var hasCustomParser = field.meta.has(jcustom); 352 | if (hasCustomParser){ 353 | try { 354 | reader = field.meta.extract(jcustom)[0].params[0]; 355 | validateCustomParser(field.type, reader); 356 | } catch(e:CustomFunctionError){ 357 | Context.fatalError(invalidParserErrorMessage(field.type, reader, e.message), Context.currentPos()); 358 | } 359 | } 360 | 361 | var assignation = if (needReflect) { 362 | macro { 363 | loadObjectFieldReflect($reader, field, $v{field.name}, assigned, pos); 364 | }; 365 | } else if (nullCheck) { 366 | macro { 367 | var v = loadObjectField($reader, field, $v{field.name}, assigned, $f_a, pos); 368 | if (v != null) { 369 | $f_a = cast v; 370 | } else { 371 | $f_a = null; 372 | } 373 | }; 374 | } else if (canRead) { 375 | macro { 376 | $f_a = cast loadObjectField($reader, field, $v{field.name}, assigned, $f_a, pos); 377 | }; 378 | } else { 379 | macro { 380 | var v = loadObjectField($reader, field, $v{field.name}, assigned, $f_a, pos); 381 | if (v != null) { 382 | $f_a = cast v; 383 | } 384 | }; 385 | } 386 | 387 | var caseValue = null; 388 | for (m in field.meta.get()) { 389 | if (m.name == ":alias" && m.params.length == 1) { 390 | switch (m.params[0].expr) { 391 | case EConst(CString(_)): 392 | caseValue = m.params[0]; 393 | default: 394 | } 395 | } 396 | } 397 | 398 | if (caseValue == null) { 399 | caseValue = { expr: EConst(CString(field.name)), pos: Context.currentPos()}; 400 | } 401 | 402 | cases.push({ expr: assignation, guard: null, values: [caseValue] }); 403 | 404 | if (field.meta.has(":default")) { 405 | var metas = field.meta.extract(":default"); 406 | if (metas.length > 0) { 407 | var meta = metas[0]; 408 | if (meta.params != null && meta.params.length == 1) { 409 | if (meta.params[0].toString() == "auto") { 410 | baseValues.push({field:field.name, expr:macro new $f_cls([], putils, NONE).getAuto() #if (haxe_ver >= 4) , quotes:Unquoted #end}); 411 | } 412 | else { 413 | baseValues.push({ field: field.name, expr: { expr: ECheckType(meta.params[0], f_type.toComplexType()), pos: meta.params[0].pos } #if (haxe_ver >= 4) , quotes: Unquoted #end }); 414 | } 415 | } 416 | } 417 | } 418 | else { 419 | var e = switch(field.type) { 420 | case TAbstract(_.get() => t, _) if (t.name == "Any"): macro null; 421 | case TLazy(_) | TDynamic(_): macro null; 422 | default: macro new $f_cls([], putils, NONE).loadJson({value:JNull, pos:{file:"",min:0, max:1}}); 423 | } 424 | baseValues.push({field:field.name, expr:e #if (haxe_ver >= 4) , quotes:Unquoted #end}); 425 | } 426 | 427 | if (needReflect) { 428 | autoExprs.push(macro Reflect.setField(value, $v{field.name}, $e{baseValues[baseValues.length - 1].expr})); 429 | } 430 | else { 431 | autoExprs.push(macro $f_a = ${baseValues[baseValues.length - 1].expr}); 432 | } 433 | 434 | default: 435 | } 436 | } 437 | 438 | var default_e = macro errors.push(UnknownVariable(field.name, putils.convertPosition(field.namePos))); 439 | var loop = { expr: ESwitch(macro field.name, cases, default_e), pos: Context.currentPos() }; 440 | 441 | if (isAnon) { 442 | initializator = { expr: EObjectDecl(baseValues), pos: Context.currentPos() }; 443 | changeFunction("getAuto", parser, macro return $initializator); 444 | } 445 | else { 446 | var casting = 447 | if (isPrivate != null && Context.defined("cpp") && !Context.defined("cppia")) 448 | { 449 | // hxcpp can't directly use the cast 450 | var abstractType = superType.toComplexType(); 451 | macro cast ((cpp.Pointer.addressOf(value).reinterpret() : cpp.Pointer<$abstractType>).value); 452 | } 453 | else 454 | { 455 | macro cast value; 456 | } 457 | 458 | var autoExpr = macro { 459 | var value = $initializator; 460 | @:privateAccess { 461 | $b{autoExprs}; 462 | } 463 | return $casting; 464 | } 465 | changeFunction("getAuto", parser, macro return $autoExpr); 466 | } 467 | 468 | var assignedKeys = { expr: EArrayDecl(assignedKeys), pos: Context.currentPos() }; 469 | var assignedValues = { expr: EArrayDecl(assignedValues), pos: Context.currentPos() }; 470 | 471 | var e = macro { 472 | var assigned = new Map(); 473 | objectSetupAssign(assigned, $assignedKeys, $assignedValues); 474 | value = getAuto(); 475 | @:privateAccess { 476 | for (field in o) { 477 | $loop; 478 | } 479 | } 480 | objectErrors(assigned, pos); 481 | }; 482 | 483 | changeFunction("loadJsonObject", parser, e); 484 | changeFunction("loadJsonNull", parser, macro {value = null;}); 485 | } 486 | 487 | public static function makeMapParser(parser:TypeDefinition, key:Type, value:Type, baseParser:BaseType) { 488 | 489 | var k_cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(key.toComplexType())]}; 490 | var keyMacro = switch (key.followWithAbstracts()) { 491 | case TInst(_.get()=>t, _): 492 | if (t.module == "String") { 493 | macro try { 494 | new $k_cls(errors, putils, THROW).loadJson({value:JString(field.name), pos:putils.revert(pos)}, variable); 495 | } catch (e:json2object.Error.InternalError) { 496 | if (e != ParsingThrow) { 497 | throw e; 498 | } 499 | 500 | continue; 501 | } 502 | } 503 | else { 504 | Context.fatalError("json2object: Only maps with Int or String keys are parsable, got "+key.toString(), callPosition); 505 | } 506 | case TAbstract(_.get()=>t, _): 507 | if (t.module == "StdTypes" && t.name == "Int") { 508 | macro try { 509 | new $k_cls(errors, putils, THROW).loadJson({value:JNumber(field.name), pos:putils.revert(pos)}, variable); 510 | } catch (e:json2object.Error.InternalError) { 511 | if (e != ParsingThrow) { 512 | throw e; 513 | } 514 | 515 | continue; 516 | } 517 | } 518 | else { 519 | Context.fatalError("json2object: Only maps with Int or String keys are parsable, got "+key.toString(), callPosition); 520 | } 521 | default: Context.fatalError("json2object: Only maps with Int or String keys are parsable, got "+key.toString(), callPosition); 522 | } 523 | 524 | var v_cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(value.toComplexType())]}; 525 | var valueMacro = macro { 526 | try { 527 | new $v_cls(errors, putils, THROW).loadJson(field.value, field.name); 528 | } 529 | catch (e:json2object.Error.InternalError) { 530 | if (e != ParsingThrow) { 531 | throw e; 532 | } 533 | 534 | continue; 535 | } 536 | }; 537 | 538 | var cls = {name:"Map", pack:[#if (haxe_ver >= 4)"haxe", "ds"#end], params:[TPType(key.toComplexType()), TPType(value.toComplexType())]}; 539 | 540 | var e = macro { 541 | value = cast new $cls(); 542 | for (field in o) { 543 | value.set($keyMacro, $valueMacro); 544 | } 545 | } 546 | 547 | changeFunction("loadJsonObject", parser, e); 548 | changeFunction("loadJsonNull", parser, macro {value = null;}); 549 | } 550 | 551 | public static function makeEnumParser(parser:TypeDefinition, type:Type, baseParser:BaseType) { 552 | 553 | var objMacro:Expr; 554 | var strMacro:Expr; 555 | 556 | var typeName:String; 557 | switch (type) { 558 | case TEnum(_.get()=>t, p): 559 | typeName = t.name; 560 | var internStringCases = new Array(); 561 | var internObjectCases = new Array(); 562 | for (n in t.names) { 563 | 564 | var l = t.module.split("."); 565 | l.push(t.name); 566 | l.push(n); 567 | var subExpr = {expr:EConst(CIdent(l.shift())), pos:Context.currentPos()}; 568 | while (l.length > 0) { 569 | subExpr = {expr:EField(subExpr, l.shift()), pos:Context.currentPos()}; 570 | } 571 | 572 | switch (t.constructs.get(n).type) { 573 | case TEnum(_,_): 574 | subExpr = macro value = cast ${subExpr}; 575 | internStringCases.push({expr: subExpr, guard: null, values: [macro $v{n}]}); 576 | 577 | var objSubExpr = macro if (s0.length == 0) { 578 | $subExpr; 579 | } else { 580 | errors.push(InvalidEnumConstructor(field.name, $v{t.name}, pos)); 581 | parsingThrow(); 582 | }; 583 | internObjectCases.push({expr: objSubExpr, guard: null, values: [macro $v{n}]}); 584 | 585 | case TFun(args, _): 586 | var names = [for (a in args) a.name]; 587 | 588 | var enumParams:Array = []; 589 | var blockExpr = [ macro var _names = $v{names} ]; 590 | blockExpr.push( 591 | macro if (s0.length != $v{args.length} || s0.filter(function (_v) { return _names.indexOf(_v.name) != -1;}).length != s0.length) { 592 | errors.push(InvalidEnumConstructor(field.name, $v{t.name}, pos)); 593 | parsingThrow(); 594 | } 595 | ); 596 | var argCount = 0; 597 | for (a in args) { 598 | var arg_name = '__${a.name}'; 599 | var at = a.t.applyTypeParameters(t.params, p); 600 | enumParams.push(macro $i{arg_name}); 601 | blockExpr.push({expr: EVars([{name: arg_name, type:at.toComplexType(), expr:null}]), pos:Context.currentPos()}); 602 | 603 | var a_cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(at.toComplexType())]}; 604 | var v = macro $i{arg_name} = new $a_cls(errors, putils, THROW).loadJson(s0.filter(function (o) { return o.name == _names[$v{argCount}];})[0].value, field.name+"."+$v{a.name}); 605 | blockExpr.push(v); 606 | argCount++; 607 | } 608 | 609 | subExpr = (enumParams.length > 0) 610 | ? {expr:ECall(subExpr, enumParams), pos:Context.currentPos()} 611 | : subExpr; 612 | blockExpr.push(macro value = cast ${subExpr}); 613 | 614 | var lil_expr:Expr = {expr: EBlock(blockExpr), pos:Context.currentPos()}; 615 | internObjectCases.push({ expr: lil_expr, guard: null, values: [{ expr: EConst(CString(n)), pos: Context.currentPos()}] }); 616 | 617 | 618 | default: 619 | } 620 | } 621 | var default_e = macro { 622 | errors.push(IncorrectEnumValue(variable, $v{t.name}, pos)); 623 | parsingThrow(); 624 | }; 625 | objMacro = {expr: ESwitch(macro field.name, internObjectCases, default_e), pos: Context.currentPos() }; 626 | objMacro = macro if (o.length != 1) { 627 | errors.push(IncorrectType(variable, $v{typeName}, pos)); 628 | parsingThrow(); 629 | } else { 630 | var field = o[0]; 631 | switch (o[0].value.value) { 632 | case JObject(s0): 633 | ${objMacro}; 634 | default: 635 | errors.push(IncorrectType(field.name, $v{typeName}, putils.convertPosition(field.value.pos))); 636 | parsingThrow(); 637 | } 638 | } 639 | strMacro = {expr: ESwitch(macro $i{"s"}, internStringCases, default_e), pos: Context.currentPos() }; 640 | default: 641 | } 642 | 643 | changeFunction("loadJsonObject", parser, objMacro); 644 | changeFunction("loadJsonString", parser, strMacro); 645 | changeFunction("loadJsonNull", parser, macro { value = null; }); 646 | } 647 | 648 | public static function makeAbstractEnumParser(parser:TypeDefinition, type:Type, baseParser:BaseType) { 649 | var name:String; 650 | 651 | switch (type.followWithAbstracts()) { 652 | case TInst(_.get()=>t, _): 653 | if (t.module != "String") { 654 | Context.fatalError("json2object: Unsupported abstract enum type:"+type.toString(), callPosition); 655 | } 656 | name = "String"; 657 | case TAbstract(_.get()=>t, _): 658 | if (t.module != "StdTypes" && (t.name != "Int" && t.name != "Bool" && t.name != "Float")) { 659 | Context.fatalError("json2object: Unsupported abstract enum type:"+type.toString(), callPosition); 660 | } 661 | name = t.name; 662 | default: Context.fatalError("json2object: Unsupported abstract enum type:"+type.toString(), callPosition); 663 | } 664 | 665 | var caseValues = new Array(); 666 | 667 | var e = macro null; 668 | 669 | switch (type) { 670 | case TAbstract(_.get()=>t, p) : 671 | for (field in t.impl.get().statics.get()) { 672 | if (!field.meta.has(":enum") || !field.meta.has(":impl")) { 673 | continue; 674 | } 675 | if (field.expr() == null) { continue; } 676 | caseValues.push( 677 | switch (field.expr().expr) { 678 | case TConst(_): Context.getTypedExpr(field.expr()); 679 | case TCast(caste, _): 680 | switch (caste.expr) { 681 | case TConst(tc): 682 | switch (tc) { 683 | case TNull: continue; 684 | default: Context.getTypedExpr(caste); 685 | } 686 | default: Context.getTypedExpr(caste); 687 | } 688 | default: continue; 689 | } 690 | ); 691 | } 692 | 693 | if (caseValues.length == 0 && !isNullable(type)) { 694 | Context.fatalError("json2object: Abstract enum of type "+ type.toString() +"can't be parsed if empty", callPosition); 695 | } 696 | 697 | var v = switch (name) { 698 | case "String": macro s; 699 | case "Int", "Float": 700 | var cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(Context.getType(name).toComplexType())]} ; 701 | macro new $cls([], putils, NONE).loadJson({value:JNumber(f), pos:putils.revert(pos)}, variable); 702 | case "Bool": macro b; 703 | default: macro null; 704 | } 705 | 706 | if (caseValues.length > 0) { 707 | if (name == "String") { 708 | var values = { expr: EArrayDecl(caseValues), pos: Context.currentPos() }; 709 | e = macro { 710 | value = cast loadString(s, pos, variable, $values, ${caseValues[0]}); 711 | }; 712 | } else { 713 | var case_e = [{expr:macro value = cast $v, guard:null, values:caseValues}]; 714 | var default_e = macro {value = cast ${caseValues[0]}; onIncorrectType(pos, variable);}; 715 | 716 | e = {expr: ESwitch(macro cast $v, case_e, default_e), pos: Context.currentPos() }; 717 | } 718 | } 719 | else { 720 | e = macro null; 721 | } 722 | 723 | var defaultValue = (caseValues.length == 0) ? macro null : macro cast ${caseValues[0]}; 724 | 725 | changeFunction("onIncorrectType", parser, macro { 726 | value = ${defaultValue}; 727 | errors.push(IncorrectType(variable, $v{type.toString()}, pos)); 728 | objectThrow(pos, variable); 729 | }); 730 | 731 | if (isNullable(t.type)) { 732 | changeFunction("loadJsonNull", parser, macro {value = cast null;}); 733 | } 734 | default: 735 | } 736 | 737 | switch (name) { 738 | case "String": 739 | changeFunction("loadJsonString", parser, e); 740 | case "Int", "Float": 741 | changeFunction("loadJsonNumber", parser, e); 742 | case "Bool": 743 | changeFunction("loadJsonBool", parser, e); 744 | default: 745 | } 746 | } 747 | 748 | public static function makeAbstractParser(parser:TypeDefinition, type:Type, baseParser:BaseType) { 749 | var hasFromFloat = false; 750 | var hasOneFrom = false; 751 | 752 | switch (type) { 753 | case TAbstract(_.get()=>t, p): 754 | var from = (t.from.length == 0) ? [{t:t.type, field:null}] : t.from; 755 | var i = 0; 756 | for(fromType in from) { 757 | var fromTypeT = fromType.t.applyTypeParameters(t.params, p); 758 | switch (fromTypeT.followWithAbstracts()) { 759 | case TInst(_.get()=>st, sp): 760 | if (st.module == "String") { 761 | if (i == 0) { makeStringParser(parser); } 762 | else { 763 | var cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(fromTypeT.toComplexType())]}; 764 | changeFunction("loadJsonString", 765 | parser, 766 | macro { 767 | value = new $cls(errors, putils, NONE).loadJson( 768 | {value:JString(s), pos:putils.revert(pos)}, 769 | variable); 770 | }); 771 | changeFunction("loadJsonNull", parser, macro {value = null;}); 772 | } 773 | hasOneFrom = true; 774 | } 775 | else if (st.module == "Array") { 776 | var subType = sp[0]; 777 | for (i in 0...t.params.length) { 778 | if (subType.unify(t.params[i].t)) { 779 | subType = p[i]; 780 | break; 781 | } 782 | } 783 | 784 | hasOneFrom = true; 785 | if (i == 0) { 786 | makeArrayParser(parser,subType.followWithAbstracts(), baseParser); 787 | } 788 | else if (isBaseType(subType.followWithAbstracts())) { 789 | var aParams = switch (fromTypeT.followWithAbstracts()) { 790 | case TInst(r,_): [TPType(TInst(r,[subType]).toComplexType())]; 791 | default:[]; 792 | } 793 | var cls = {name:baseParser.name, pack:baseParser.pack, params:aParams}; 794 | changeFunction("loadJsonArray", 795 | parser, 796 | macro { 797 | value = new $cls(errors, putils, NONE).loadJson( 798 | {value:JArray(a), pos:putils.revert(pos)}, 799 | variable); 800 | }); 801 | changeFunction("loadJsonNull", parser, macro {value = null;}); 802 | } 803 | else { 804 | hasOneFrom = false; 805 | } 806 | } 807 | else { 808 | if (i == 0) { 809 | var t = fromTypeT; 810 | if (st.isPrivate) { 811 | var privateType = TypeUtils.copyType(st); 812 | t = Context.getType(privateType.name); 813 | } 814 | 815 | var cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(t.toComplexType())]}; 816 | var casting = 817 | if (st.isPrivate && Context.defined("cpp") && !Context.defined("cppia")) 818 | { 819 | // hxcpp can't directly use the cast 820 | var abstractType = type.toComplexType(); 821 | macro { 822 | var __tmp__new = new $cls(errors, putils, NONE).loadJson( 823 | {value:JObject(o), pos:putils.revert(pos)}, 824 | variable); 825 | cast ((cpp.Pointer.addressOf(__tmp__new).reinterpret() : cpp.Pointer<$abstractType>).value); 826 | } 827 | } 828 | else if (st.isPrivate && (Context.defined("cs") || Context.defined("java") || Context.defined("hl"))) 829 | { 830 | Context.fatalError("json2object: Abstract of private are not supported on this target", callPosition); 831 | } 832 | else 833 | { 834 | macro cast new $cls(errors, putils, NONE).loadJson( 835 | {value:JObject(o), pos:putils.revert(pos)}, 836 | variable); 837 | } 838 | changeFunction("loadJsonObject", parser, macro { 839 | value = $casting; 840 | }); 841 | changeFunction("loadJsonNull", parser, macro {value = null;}); 842 | hasOneFrom = true; 843 | } 844 | } 845 | case TAbstract(_.get()=>st, sp): 846 | if (st.module == "StdTypes") { 847 | var cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(fromTypeT.toComplexType())]}; 848 | switch (st.name) { 849 | case "Int": 850 | if (!hasFromFloat) { 851 | if (i == 0) { 852 | makeIntParser(parser, fromTypeT); 853 | } 854 | else { 855 | changeFunction("loadJsonNumber", 856 | parser, 857 | macro { 858 | value = new $cls(errors, putils, NONE).loadJson( 859 | {value:JNumber(f), pos:putils.revert(pos)}, 860 | variable); 861 | }); 862 | } 863 | hasOneFrom = true; 864 | } 865 | case "Float": 866 | if (i == 0) { 867 | makeFloatParser(parser, fromTypeT); 868 | } 869 | else { 870 | changeFunction("loadJsonNumber", 871 | parser, 872 | macro { 873 | value = new $cls(errors, putils, NONE).loadJson( 874 | {value:JNumber(f), pos:putils.revert(pos)}, 875 | variable); 876 | }); 877 | } 878 | hasFromFloat = true; 879 | hasOneFrom = true; 880 | case "Bool": 881 | if (i == 0) { 882 | makeBoolParser(parser, fromTypeT); 883 | } 884 | else { 885 | changeFunction("loadJsonBool", 886 | parser, 887 | macro { 888 | value = new $cls(errors, putils, NONE).loadJson( 889 | {value:JBool(b), pos:putils.revert(pos)}, 890 | variable); 891 | }); 892 | } 893 | hasOneFrom = true; 894 | } 895 | } 896 | else if (i == 0 && t.module == #if (haxe_ver >= 4) "haxe.ds.Map" #else "Map" #end) { 897 | var key = sp[0]; 898 | var value = sp[1]; 899 | for (i in 0...t.params.length) { 900 | if (key.unify(t.params[i].t)) { 901 | key = p[i]; 902 | } 903 | if (value.unify(t.params[i].t)) { 904 | value = p[i]; 905 | } 906 | } 907 | makeMapParser(parser, key, value, baseParser); 908 | hasOneFrom = true; 909 | } 910 | case TAnonymous(_.get()=>st): 911 | if (i == 0) { 912 | var cls = {name:baseParser.name, pack:baseParser.pack, params:[TPType(fromTypeT.toComplexType())]}; 913 | changeFunction("loadJsonObject", parser, macro { 914 | value = cast new $cls(errors, putils, NONE).loadJson( 915 | {value:JObject(o), pos:putils.revert(pos)}, 916 | variable); 917 | }); 918 | changeFunction("loadJsonNull", parser, macro {value = null;}); 919 | hasOneFrom = true; 920 | } 921 | default: 922 | } 923 | i++; 924 | } 925 | 926 | if (isNullable(t.type)) { 927 | changeFunction("loadJsonNull", parser, macro {value = cast null;}); 928 | } 929 | default: 930 | } 931 | if (!hasOneFrom) { 932 | Context.fatalError("json2object: No parser can be generated for "+type.toString()+ " as it has no supported @:from", callPosition); 933 | } 934 | } 935 | 936 | public static function makeParser(c:BaseType, type:Type, ?base:Type=null) { 937 | if (base == null) { base = type; } 938 | 939 | var parserMapName = base.toString(); 940 | if (parsers.exists(parserMapName)) { 941 | return parsers.get(parserMapName); 942 | } 943 | 944 | var defaultValueExpr:Expr = switch (type) { 945 | case TAbstract(_.get()=>t,_): 946 | switch (t.name) { 947 | case "Int", "Float", "Single" if (!isNullable(base) && t.module == "StdTypes"): 948 | macro value = 0; 949 | case "UInt" if (!isNullable(base) && t.module == "UInt"): 950 | macro value = 0; 951 | case "Bool" if (!isNullable(base) && t.module == "StdTypes"): 952 | macro value = false; 953 | default: macro {}; 954 | } 955 | default: macro {}; 956 | } 957 | 958 | var parserName = c.name + "_" + (counter++); 959 | var parent = {name:"BaseParser", pack:["json2object", "reader"], params:[TPType(base.toComplexType())]}; 960 | var parser = macro class $parserName extends $parent { 961 | public function new(?errors:Array=null, ?putils:json2object.PositionUtils=null, ?errorType:json2object.Error.ErrorType=json2object.Error.ErrorType.NONE) { 962 | super(errors, putils, errorType); 963 | ${defaultValueExpr} 964 | } 965 | 966 | override private function onIncorrectType(pos:json2object.Position, variable:String) { 967 | errors.push(IncorrectType(variable, $v{type.toString()}, pos)); 968 | super.onIncorrectType(pos, variable); 969 | } 970 | 971 | override private function loadJsonNull(pos:json2object.Position, variable:String) { 972 | } 973 | override private function loadJsonString(s:String, pos:json2object.Position, variable:String) { 974 | } 975 | override private function loadJsonNumber(f:String, pos:json2object.Position, variable:String) { 976 | } 977 | override private function loadJsonBool(b:Bool, pos:json2object.Position, variable:String) { 978 | } 979 | override private function loadJsonArray(a:Array, pos:json2object.Position, variable:String) { 980 | } 981 | override private function loadJsonObject(o:Array, pos:json2object.Position, variable:String) { 982 | } 983 | }; 984 | 985 | if (Context.defined("cs")) { 986 | // C# fix for conversion with baseparser 987 | parser.meta.push({ 988 | name: ":nativeGen", 989 | params: null, 990 | pos: Context.currentPos() 991 | }); 992 | } 993 | 994 | var parser_cls = { name: parserName, pack: [], params: null, sub: null }; 995 | var getAutoExpr = macro return new $parser_cls([], putils, NONE).loadJson({value:JNull, pos:{file:"",min:0, max:1}}); 996 | var getAuto:Field = { 997 | doc: null, 998 | kind: FFun({args:[], expr:getAutoExpr, params:null, ret:TypeTools.toComplexType(base)}), 999 | access: [APublic], 1000 | name: "getAuto", 1001 | pos:Context.currentPos(), 1002 | meta: null 1003 | } 1004 | parser.fields.push(getAuto); 1005 | 1006 | switch (type) { 1007 | case TInst(_.get()=>t, p) : 1008 | switch(t.module) { 1009 | case "String": 1010 | makeStringParser(parser); 1011 | case "Array" if (p.length == 1 && p[0] != null): 1012 | makeArrayParser(parser, p[0], c); 1013 | case "List" | "haxe.ds.List" if (p.length == 1 && p[0] != null): 1014 | makeListParser(parser, p[0], c); 1015 | case _: 1016 | switch (t.kind) { 1017 | case KTypeParameter(_): 1018 | Context.fatalError("json2object: Type parameters are not parsable: " + t.name, callPosition); 1019 | 1020 | default: 1021 | } 1022 | if (t.meta.has(jcustom)){ 1023 | makeCustomParser(parser, type, t); 1024 | } else { 1025 | makeObjectOrAnonParser(parser, type, null, c); 1026 | } 1027 | } 1028 | case TAnonymous(_): 1029 | makeObjectOrAnonParser(parser, type, null, c); 1030 | case TAbstract(_.get()=>t, p): 1031 | if (t.name == "Null") { 1032 | return makeParser(c, p[0], type); 1033 | } 1034 | else if (t.name == "Any") { 1035 | Context.fatalError("json2object: Parser of "+t.name+" are not generated", callPosition); 1036 | } 1037 | else if (t.module == "UInt" && t.name == "UInt") { 1038 | makeUIntParser(parser, base); 1039 | } 1040 | else if (t.module == "StdTypes") { 1041 | switch (t.name) { 1042 | case "Int" : 1043 | makeIntParser(parser, base); 1044 | case "Float", "Single": 1045 | makeFloatParser(parser, base); 1046 | case "Bool": 1047 | makeBoolParser(parser, base); 1048 | default: Context.fatalError("json2object: Parser of "+t.name+" are not generated", callPosition); 1049 | } 1050 | } 1051 | else if (t.module == #if (haxe_ver >= 4) "haxe.ds.Map" #else "Map" #end) { 1052 | makeMapParser(parser, p[0], p[1], c); 1053 | } 1054 | else { 1055 | if (t.meta.has(":enum")) { 1056 | makeAbstractEnumParser(parser, type.applyTypeParameters(t.params, p), c); 1057 | } 1058 | else if (t.meta.has(":coreType")) { 1059 | Context.fatalError("json2object: Parser of coreType ("+t.name+") are not generated", Context.currentPos()); 1060 | } 1061 | else { 1062 | makeAbstractParser(parser, type.applyTypeParameters(t.params, p), c); 1063 | } 1064 | } 1065 | case TEnum(_.get()=>t, p): 1066 | makeEnumParser(parser, type.applyTypeParameters(t.params, p), c); 1067 | case TType(_.get()=>t, p) : 1068 | return makeParser(c, t.type.applyTypeParameters(t.params, p), type); 1069 | case TLazy(f): 1070 | return makeParser(c, f()); 1071 | default: Context.fatalError("json2object: Parser of "+type.toString()+" are not generated", callPosition); 1072 | } 1073 | 1074 | parser.fields = parser.fields.filter(function (field) { 1075 | return switch (field.kind) { 1076 | case FFun({expr:{expr:EBlock([])}}): false; 1077 | default: true; 1078 | } 1079 | }); 1080 | 1081 | haxe.macro.Context.defineType(parser); 1082 | 1083 | var constructedType = haxe.macro.Context.getType(parserName); 1084 | parsers.set(parserMapName, constructedType); 1085 | return constructedType; 1086 | 1087 | } 1088 | 1089 | public static function build() { 1090 | switch (Context.getLocalType()) { 1091 | case TInst(c, [type]): 1092 | var pos = Context.getPosInfos(Context.currentPos()); 1093 | if (pos.min != -1 && pos.max != -1) { 1094 | callPosition = Context.makePosition(pos); 1095 | } 1096 | return makeParser(c.get(), type); 1097 | case _: 1098 | Context.fatalError("json2object: Parsing tools must be a class", Context.currentPos()); 1099 | return null; 1100 | } 1101 | } 1102 | } 1103 | #end 1104 | --------------------------------------------------------------------------------