├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example.cr ├── example2.cr ├── shard.yml ├── spec ├── after_initialize_spec.cr ├── auto_expand_spec.cr ├── complex_spec.cr ├── emit_null_spec.cr ├── empty_spec.cr ├── enable_fields_spec.cr ├── inheritance_spec.cr ├── json_methods_spec.cr ├── key_spec.cr ├── nested_data_spec.cr ├── nested_spec.cr ├── nilable_and_default_spec.cr ├── simple_edge_cases_spec.cr ├── simple_spec.cr ├── spec_helper.cr ├── std_mapping_spec.cr ├── union_spec.cr ├── with_defaults_bool_spec.cr ├── with_defaults_spec.cr └── with_nilable_spec.cr └── src └── auto_json.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | 6 | # Libraries don't need dependency lock 7 | # Dependencies will be locked in application that uses them 8 | /shard.lock 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Konstantin Makarchev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT DEPRECATED, just use JSON::Serializable module instead. 2 | 3 | # auto_json 4 | 5 | Auto JSON convertations for classes and structs, based on [auto_constructor](https://github.com/kostya/auto_constructor) fields 6 | 7 | ## Installation 8 | 9 | Add this to your application's `shard.yml`: 10 | 11 | ```yaml 12 | dependencies: 13 | auto_json: 14 | github: kostya/auto_json 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```crystal 20 | require "auto_json" 21 | 22 | struct A 23 | include AutoJson 24 | 25 | field :a, Int32 26 | field :b, String, default: "def" 27 | field :c, Int32 28 | field :d, String? 29 | field :e, Float64 30 | end 31 | 32 | puts A.new(a: 1, c: 3, e: 1.0).to_json # => {"a":1,"b":"def","c":3,"e":1.0} 33 | puts A.from_json(%q<{"a":1,"c":3,"e":1.1}>) # => A(@a=1, @b="def", @c=3, @d=nil, @e=1.1) 34 | ``` 35 | 36 | ## Field options 37 | 38 | ``` 39 | :key - set serialized key 40 | :json_key - set serialized key for json 41 | :serialize - serialize field? [true] 42 | :json - serialize field for json? [true] 43 | :converter - converter 44 | :json_converter - json_converter 45 | :emit_null - emit null in generate 46 | :json_emit_null - emit null in generate json 47 | ``` 48 | 49 | ## Advanced Example 50 | 51 | ```crystal 52 | require "auto_json" 53 | 54 | struct A 55 | include AutoJson 56 | 57 | json_options(strict: true, emit_nulls: true) 58 | 59 | field :a, Int32, key: "bla" 60 | field :b, String, default: "def" 61 | field :c, Int32, json_key: "blc" 62 | field :d, String?, json_emit_null: true 63 | field :e, Float64, default: 1.1, json: false 64 | field :t, Time?, json_converter: Time::Format.new("%F %T") 65 | end 66 | 67 | a = A.new(a: 1, b: "b", c: 2, t: Time.now) 68 | json = a.to_json 69 | puts json # => {"bla":1,"b":"b","blc":2,"d":null,"t":"2017-02-02 01:38:31"} 70 | 71 | a2 = A.from_json(json) 72 | p a2 # => A(@a=1, @b="b", @c=2, @d=nil, @e=1.1, @t=2017-02-02 01:38:31) 73 | ``` 74 | -------------------------------------------------------------------------------- /example.cr: -------------------------------------------------------------------------------- 1 | require "./src/auto_json" 2 | 3 | struct A 4 | include AutoJson 5 | 6 | field :a, Int32 7 | field :b, String, default: "def" 8 | field :c, Int32 9 | field :d, String? 10 | field :e, Float64 11 | end 12 | 13 | puts A.new(a: 1, c: 3, e: 1.0).to_json # => {"a":1,"b":"def","c":3,"e":1.0} 14 | puts A.from_json(%q<{"a":1,"c":3,"e":1.1}>) # => A(@a=1, @b="def", @c=3, @d=nil, @e=1.1) 15 | -------------------------------------------------------------------------------- /example2.cr: -------------------------------------------------------------------------------- 1 | require "./src/auto_json" 2 | 3 | struct A 4 | include AutoJson 5 | 6 | json_options(strict: true, emit_nulls: true) 7 | 8 | field :a, Int32, key: "bla" 9 | field :b, String, default: "def" 10 | field :c, Int32, json_key: "blc" 11 | field :d, String?, json_emit_null: true 12 | field :e, Float64, default: 1.1, json: false 13 | field :t, Time?, json_converter: Time::Format.new("%F %T") 14 | end 15 | 16 | a = A.new(a: 1, b: "b", c: 2, t: Time.now) 17 | json = a.to_json 18 | puts json # => {"bla":1,"b":"b","blc":2,"d":null,"t":"2017-02-02 01:38:31"} 19 | 20 | a2 = A.from_json(json) 21 | p a2 # => A(@a=1, @b="b", @c=2, @d=nil, @e=1.1, @t=2017-02-02 01:38:31) 22 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: auto_json 2 | version: 0.2 3 | 4 | authors: 5 | - Konstantin Makarchev 6 | 7 | targets: 8 | auto_json: 9 | main: src/auto_json.cr 10 | 11 | crystal: 0.20.5 12 | 13 | dependencies: 14 | auto_constructor: 15 | github: kostya/auto_constructor 16 | version: 0.4 17 | 18 | license: MIT 19 | -------------------------------------------------------------------------------- /spec/after_initialize_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class AfterInitialize1 4 | include AutoJson 5 | field x, Int32 6 | 7 | property y : Int32 8 | property z : Int32 9 | 10 | after_initialize do 11 | @y = @x + 1 12 | @z = @y + 1 13 | end 14 | end 15 | 16 | describe AutoConstructor do 17 | context "after_initialize" do 18 | it do 19 | a = AfterInitialize1.from_json(%q<{"x":1}>) 20 | a.x.should eq 1 21 | a.y.should eq 2 22 | a.z.should eq 3 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/auto_expand_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class AutoExpand 4 | include AutoJson 5 | field :x, Int32 6 | end 7 | 8 | class AutoExpand 9 | field :y, String 10 | end 11 | 12 | describe AutoConstructor do 13 | context "auto expand class" do 14 | it do 15 | a = AutoExpand.from_json(%q<{"x":1,"y":"bla"}>) 16 | a.x.should eq 1 17 | a.y.should eq "bla" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/complex_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class ComplexSimple 4 | include AutoJson 5 | field x, Int32 6 | end 7 | 8 | class Complex 9 | include AutoJson 10 | field :x, Int32 11 | field :y, String, default: "def" 12 | field :z, Int32 13 | field :d, String? 14 | field :b, ComplexSimple, default: ComplexSimple.new(1) 15 | end 16 | 17 | describe AutoJson do 18 | context "complex" do 19 | it do 20 | c = Complex.from_json(%q<{"x":1,"z":2,"b":{"x":4}}>) 21 | c.x.should eq 1 22 | c.y.should eq "def" 23 | c.z.should eq 2 24 | c.d.should eq nil 25 | c.b.x.should eq 4 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/emit_null_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct EmitNulls1 4 | include AutoJson 5 | 6 | json_options(emit_nulls: true) 7 | 8 | field :a, Int32? 9 | field :b, String? 10 | end 11 | 12 | struct EmitNulls2 13 | include AutoJson 14 | 15 | field :a, Int32?, emit_null: true 16 | field :b, String?, json_emit_null: true 17 | end 18 | 19 | context "emit_null" do 20 | it { EmitNulls1.new.to_json.should eq %q<{"a":null,"b":null}> } 21 | it { EmitNulls1.new(1).to_json.should eq %q<{"a":1,"b":null}> } 22 | 23 | it { EmitNulls2.new.to_json.should eq %q<{"a":null,"b":null}> } 24 | it { EmitNulls2.new(1).to_json.should eq %q<{"a":1,"b":null}> } 25 | end 26 | -------------------------------------------------------------------------------- /spec/empty_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class Empty 4 | include AutoJson 5 | end 6 | 7 | describe AutoJson do 8 | context "converts" do 9 | it { Empty.new.to_json.should eq "{}" } 10 | it { Empty.from_json("{}").should be_a(Empty) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/enable_fields_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct EnabledFields 4 | include AutoJson 5 | 6 | field :a, Int32, default: 10, serialize: false 7 | field :b, Int32, default: 11, json: false 8 | field :c, Int32, default: 12 9 | end 10 | 11 | struct FullyEmpty 12 | include AutoJson 13 | 14 | field :a, Int32, default: 10, json: false 15 | field :b, Int32, default: 11, json: false 16 | end 17 | 18 | struct DisabledField 19 | include AutoJson 20 | 21 | field :a, Int32?, json: false 22 | end 23 | 24 | struct DisabledFieldNilableDefault 25 | include AutoJson 26 | 27 | field :a, Int32?, json: false, default: 11 28 | end 29 | 30 | describe AutoJson do 31 | context "FullyEmpty" do 32 | it do 33 | f = FullyEmpty.new 34 | f.a.should eq 10 35 | f.b.should eq 11 36 | f.to_json.should eq %q<{}> 37 | end 38 | 39 | it do 40 | f = FullyEmpty.from_json(%q<{}>) 41 | f.a.should eq 10 42 | f.b.should eq 11 43 | end 44 | 45 | it do 46 | f = FullyEmpty.from_json(%q<{"a":111}>) 47 | f.a.should eq 10 48 | f.b.should eq 11 49 | end 50 | end 51 | 52 | context "EnabledFields" do 53 | it do 54 | f = EnabledFields.new 55 | f.a.should eq 10 56 | f.b.should eq 11 57 | f.c.should eq 12 58 | f.to_json.should eq %q<{"c":12}> 59 | end 60 | 61 | it do 62 | f = EnabledFields.from_json(%q<{}>) 63 | f.a.should eq 10 64 | f.b.should eq 11 65 | f.c.should eq 12 66 | end 67 | 68 | it do 69 | f = EnabledFields.from_json(%q<{"a":111,"c":112}>) 70 | f.a.should eq 10 71 | f.b.should eq 11 72 | f.c.should eq 112 73 | end 74 | end 75 | 76 | context "DisabledField" do 77 | it do 78 | f = DisabledField.new(10) 79 | f.a.should eq 10 80 | f.to_json.should eq %q<{}> 81 | end 82 | 83 | it do 84 | f = DisabledField.from_json(%q<{}>) 85 | f.a.should eq nil 86 | end 87 | 88 | it do 89 | f = DisabledField.from_json(%q<{"a":111}>) 90 | f.a.should eq nil 91 | end 92 | end 93 | 94 | context "DisabledFieldNilableDefault" do 95 | it do 96 | f = DisabledFieldNilableDefault.new(10) 97 | f.a.should eq 10 98 | f.to_json.should eq %q<{}> 99 | end 100 | 101 | it do 102 | f = DisabledFieldNilableDefault.from_json(%q<{}>) 103 | f.a.should eq 11 104 | end 105 | 106 | it do 107 | f = DisabledFieldNilableDefault.from_json(%q<{"a":111}>) 108 | f.a.should eq 11 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/inheritance_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class AutoInherit1 4 | include AutoJson 5 | field :x, Int32 6 | end 7 | 8 | class AutoInherit2 < AutoInherit1 9 | field :y, String 10 | 11 | def xy 12 | {x, y} 13 | end 14 | end 15 | 16 | class AutoInherit3 < AutoInherit2 17 | field :z, String 18 | 19 | def xyz 20 | {x, y, z} 21 | end 22 | end 23 | 24 | class AutoInherit31 < AutoInherit2 25 | field :z, Int32 26 | 27 | def xyz 28 | {x, y, z} 29 | end 30 | end 31 | 32 | class AutoInherit32 < AutoInherit2 33 | field :zz, String 34 | 35 | def xyz 36 | {x, y, zz} 37 | end 38 | end 39 | 40 | class AutoInherit4 < AutoInherit3 41 | field :d, Int32 42 | 43 | def xyzd 44 | {x, y, z, d} 45 | end 46 | end 47 | 48 | describe AutoConstructor do 49 | it { AutoInherit1.new(1).to_json.should eq %q<{"x":1}> } 50 | it { AutoInherit1.from_json(%q<{"x":1}>).x.should eq 1 } 51 | 52 | it { AutoInherit2.new(1, "2").to_json.should eq %q<{"x":1,"y":"2"}> } 53 | # it { AutoInherit2.from_json(%q<{"x":1,"y":"2"}>).xy.should eq({1, "2"}) } 54 | 55 | it { AutoInherit3.new(1, "2", "z").to_json.should eq %q<{"x":1,"y":"2","z":"z"}> } 56 | # it { AutoInherit3.from_json(%q<{"x":1,"y":"2","z":"z"}>).xyz.should eq({1, "2", "z"}) } 57 | 58 | it { AutoInherit31.new(1, "2", 3).to_json.should eq %q<{"x":1,"y":"2","z":3}> } 59 | # it { AutoInherit31.from_json(%q<{"x":1,"y":"2","z":3}>).xyz.should eq({1, "2", 3}) } 60 | 61 | it { AutoInherit32.new(1, "2", "z").to_json.should eq %q<{"x":1,"y":"2","zz":"z"}> } 62 | # it { AutoInherit32.from_json(%q<{"x":1,"y":"2","zz":"z"}>).xyz.should eq({1, "2", "z"}) } 63 | 64 | it { AutoInherit4.new(1, "2", "z", 2).to_json.should eq %q<{"x":1,"y":"2","z":"z","d":2}> } 65 | # it { AutoInherit4.from_json(%q<{"x":1,"y":"2","z":"z","d":2}>).xyzd.should eq({1, "2", "z", 2}) } 66 | end 67 | -------------------------------------------------------------------------------- /spec/json_methods_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class TestMethods 4 | include AutoJson 5 | 6 | field x, Int32 7 | end 8 | 9 | describe AutoJson do 10 | it { TestMethods.new(1).pack.should eq %q<{"x":1}> } 11 | it { TestMethods.new(1).to_json.should eq %q<{"x":1}> } 12 | it { String.build { |io| TestMethods.new(1).to_json(io) }.should eq %q<{"x":1}> } 13 | it { String.build { |io| TestMethods.new(1).pack(io) }.should eq %q<{"x":1}> } 14 | it { TestMethods.from_json(%q<{"x":1}>).x.should eq 1 } 15 | it { TestMethods.unpack(%q<{"x":1}>).x.should eq 1 } 16 | 17 | it do 18 | expect_raises(JSON::ParseException, "missing json attribute: x at 0:0") do 19 | TestMethods.unpack(%q<{}>) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/key_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct WithKeys 4 | include AutoJson 5 | 6 | field :a, Int32, key: "bla" 7 | field :b, Int32, json_key: :bla2 8 | end 9 | 10 | describe AutoJson do 11 | it do 12 | WithKeys.new(1, 2).to_json.should eq %q<{"bla":1,"bla2":2}> 13 | end 14 | 15 | it do 16 | w = WithKeys.from_json(%q<{"bla":1,"bla2":2}>) 17 | w.a.should eq 1 18 | w.b.should eq 2 19 | end 20 | 21 | it do 22 | expect_raises(JSON::ParseException, "missing json attribute: bla") do 23 | WithKeys.from_json(%q<{"a":1,"b":2}>) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/nested_data_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct DataMoreFieldsSimple 4 | include AutoJson 5 | field :a, Int32 6 | end 7 | 8 | struct DataMoreFields 9 | include AutoJson 10 | 11 | field :a, Int32 12 | field :b, String 13 | field :c, Array(DataMoreFieldsSimple) 14 | end 15 | 16 | describe AutoJson do 17 | data = {DataMoreFieldsSimple.new(3), 18 | 2, 19 | DataMoreFields.new(1, "bla", [DataMoreFieldsSimple.new(1), DataMoreFieldsSimple.new(2)]), 20 | [DataMoreFields.new(1, "b", [] of DataMoreFieldsSimple)], 21 | {key: DataMoreFieldsSimple.new(10)}, 22 | {"key2" => DataMoreFieldsSimple.new(11)}, 23 | } 24 | js = "[{\"a\":3},2,{\"a\":1,\"b\":\"bla\",\"c\":[{\"a\":1},{\"a\":2}]},[{\"a\":1,\"b\":\"b\",\"c\":[]}],{\"key\":{\"a\":10}},{\"key2\":{\"a\":11}}]" 25 | 26 | it "pack" do 27 | data.to_json.should eq js 28 | end 29 | 30 | it "unpack" do 31 | typeof(data).from_json(js).should eq data 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/nested_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct MoreFieldsSimple 4 | include AutoJson 5 | field :a, Int32 6 | end 7 | 8 | struct MoreFields 9 | include AutoJson 10 | 11 | field :a, Int32 12 | field :b, String 13 | field :c, MoreFieldsSimple 14 | end 15 | 16 | describe AutoJson do 17 | context "nested" do 18 | it do 19 | s1 = MoreFieldsSimple.new(1) 20 | s2 = MoreFields.new(10, "b", s1) 21 | s2.a.should eq 10 22 | s2.b.should eq "b" 23 | s2.c.should eq s1 24 | s2.to_json.should eq "{\"a\":10,\"b\":\"b\",\"c\":{\"a\":1}}" 25 | end 26 | 27 | it do 28 | s1 = MoreFieldsSimple.new(1) 29 | s2 = MoreFields.new(a: 10, b: "b", c: s1) 30 | s2.a.should eq 10 31 | s2.b.should eq "b" 32 | s2.c.should eq s1 33 | s2.to_json.should eq "{\"a\":10,\"b\":\"b\",\"c\":{\"a\":1}}" 34 | end 35 | 36 | it do 37 | s1 = MoreFieldsSimple.new(1) 38 | s2 = MoreFields.new(c: s1, b: "b", a: 10) 39 | s2.a.should eq 10 40 | s2.b.should eq "b" 41 | s2.c.should eq s1 42 | s2.to_json.should eq "{\"a\":10,\"b\":\"b\",\"c\":{\"a\":1}}" 43 | end 44 | 45 | it "load" do 46 | s2 = MoreFields.from_json("{\"a\":10,\"b\":\"b\",\"c\":{\"a\":1}}") 47 | s2.a.should eq 10 48 | s2.b.should eq "b" 49 | s2.c.a.should eq 1 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/nilable_and_default_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class Nilable1 4 | include AutoJson 5 | field :x, Int32?, default: 1 6 | end 7 | 8 | class Nilable2 9 | include AutoJson 10 | field :x, Int32?, default: nil 11 | end 12 | 13 | class Nilable3 14 | include AutoJson 15 | field :x, Bool?, default: false 16 | end 17 | 18 | class Nilable4 19 | include AutoJson 20 | field :x, Bool?, default: true 21 | end 22 | 23 | class Nilable5 24 | include AutoJson 25 | field :x, Bool?, default: nil 26 | end 27 | 28 | describe AutoJson do 29 | context "Nilable1" do 30 | it { Nilable1.from_json(%q<{"x":2}>).x.should eq 2 } 31 | it { Nilable1.from_json(%q<{}>).x.should eq 1 } 32 | it { Nilable1.from_json(%q<{"x":null}>).x.should eq nil } 33 | end 34 | 35 | context "Nilable2" do 36 | it { Nilable2.from_json(%q<{"x":2}>).x.should eq 2 } 37 | it { Nilable2.from_json(%q<{}>).x.should eq nil } 38 | it { Nilable2.from_json(%q<{"x":null}>).x.should eq nil } 39 | end 40 | 41 | context "Nilable3" do 42 | it { Nilable3.from_json(%q<{"x":true}>).x.should eq true } 43 | it { Nilable3.from_json(%q<{"x":false}>).x.should eq false } 44 | it { Nilable3.from_json(%q<{"x":null}>).x.should eq nil } 45 | it { Nilable3.from_json(%q<{}>).x.should eq false } 46 | end 47 | 48 | context "Nilable4" do 49 | it { Nilable4.from_json(%q<{"x":true}>).x.should eq true } 50 | it { Nilable4.from_json(%q<{"x":false}>).x.should eq false } 51 | it { Nilable4.from_json(%q<{"x":null}>).x.should eq nil } 52 | it { Nilable4.from_json(%q<{}>).x.should eq true } 53 | end 54 | 55 | context "Nilable5" do 56 | it { Nilable5.from_json(%q<{"x":true}>).x.should eq true } 57 | it { Nilable5.from_json(%q<{"x":false}>).x.should eq false } 58 | it { Nilable5.from_json(%q<{"x":null}>).x.should eq nil } 59 | it { Nilable5.from_json(%q<{}>).x.should eq nil } 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/simple_edge_cases_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct Bool1 4 | include AutoJson 5 | field :x, Bool 6 | end 7 | 8 | struct Nil1 9 | include AutoJson 10 | field :x, Nil 11 | end 12 | 13 | describe "AutoJson" do 14 | context "Bool1" do 15 | it { Bool1.from_json(%q<{"x":true}>).x.should eq true } 16 | it { Bool1.new(true).to_json.should eq %q<{"x":true}> } 17 | it { Bool1.from_json(%q<{"x":false}>).x.should eq false } 18 | it { Bool1.new(false).to_json.should eq %q<{"x":false}> } 19 | end 20 | 21 | context "Nil1" do 22 | it { Nil1.from_json(%q<{"x":null}>).x.should eq nil } 23 | it { Nil1.from_json(%q<{}>).x.should eq nil } 24 | it { Nil1.new(nil).to_json.should eq %q<{}> } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/simple_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class Simple 4 | include AutoJson 5 | 6 | field :a, Int32 7 | field :b, String 8 | end 9 | 10 | describe AutoJson do 11 | context "converts" do 12 | it { Simple.new(10, "bla").to_json.should eq "{\"a\":10,\"b\":\"bla\"}" } 13 | it do 14 | s = Simple.from_json("{\"a\":10,\"b\":\"bla\"}") 15 | s.a.should eq 10 16 | s.b.should eq "bla" 17 | end 18 | 19 | it { Simple.from_json("{\"a\":10,\"b\":\"bla\"}").to_json.should eq "{\"a\":10,\"b\":\"bla\"}" } 20 | 21 | it do 22 | expect_raises(JSON::ParseException, "missing json attribute: a at 0:0") do 23 | Simple.from_json("{}") 24 | end 25 | end 26 | 27 | it "skip extra values" do 28 | Simple.from_json("{\"a\":10,\"b\":\"bla\",\"c\":1}").to_json.should eq "{\"a\":10,\"b\":\"bla\"}" 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/auto_json" 3 | -------------------------------------------------------------------------------- /spec/std_mapping_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | class JSONPerson 4 | include AutoJson 5 | field name, String 6 | field age, Int32? 7 | end 8 | 9 | private class StrictJSONPerson 10 | include AutoJson 11 | json_options strict: true 12 | field name, String 13 | field age, Int32? 14 | end 15 | 16 | private class JSONPersonEmittingNull 17 | include AutoJson 18 | field name, String 19 | field age, Int32?, emit_null: true 20 | end 21 | 22 | private class JSONWithBool 23 | include AutoJson 24 | field value, Bool 25 | end 26 | 27 | private class JSONWithTime 28 | include AutoJson 29 | field value, Time, converter: Time::Format.new("%F %T") 30 | end 31 | 32 | private class JSONWithNilableTime 33 | include AutoJson 34 | field value, Time?, converter: Time::Format.new("%F") 35 | end 36 | 37 | private class JSONWithNilableTimeEmittingNull 38 | include AutoJson 39 | field value, Time?, converter: Time::Format.new("%F"), emit_null: true 40 | end 41 | 42 | private class JSONWithSimpleMapping 43 | include AutoJson 44 | field name, String 45 | field age, Int32 46 | end 47 | 48 | private class JSONWithKeywordsMapping 49 | include AutoJson 50 | field :end, Int32 51 | field :abstract, Int32 52 | end 53 | 54 | private class JSONWithAny 55 | include AutoJson 56 | field name, String 57 | field any, JSON::Any 58 | end 59 | 60 | private class JsonWithProblematicKeys 61 | include AutoJson 62 | field key, Int32 63 | field pull, Int32 64 | end 65 | 66 | private class JsonWithSet 67 | include AutoJson 68 | field set, Set(String) 69 | end 70 | 71 | private class JsonWithDefaults 72 | include AutoJson 73 | field a, Int32, default: 11 74 | field b, String, default: "Haha" 75 | field c, Bool, default: true 76 | field d, Bool, default: false 77 | field e, Bool?, default: false 78 | field f, Int32?, default: 1 79 | field g, Int32?, default: nil 80 | field h, Array(Int32), default: [1, 2, 3] 81 | end 82 | 83 | private class JSONWithSmallIntegers 84 | include AutoJson 85 | field foo, Int16 86 | field bar, Int8 87 | end 88 | 89 | private class JSONWithTimeEpoch 90 | include AutoJson 91 | field value, Time, converter: Time::EpochConverter 92 | end 93 | 94 | private class JSONWithTimeEpochMillis 95 | include AutoJson 96 | field value, Time, converter: Time::EpochMillisConverter 97 | end 98 | 99 | private class JSONWithRaw 100 | include AutoJson 101 | field value, String, converter: String::RawConverter 102 | end 103 | 104 | # private class JSONWithRoot 105 | # include AutoJson 106 | # field result, Array(JSONPerson), root: "heroes" 107 | # end 108 | 109 | # private class JSONWithNilableRoot 110 | # include AutoJson 111 | # field result, Array(JSONPerson)?, root: "heroes" 112 | # end 113 | 114 | # private class JSONWithNilableRootEmitNull 115 | # include AutoJson 116 | # field result, Array(JSONPerson)?, root: "heroes", emit_null: true 117 | # end 118 | 119 | private class JSONWithNilableUnion 120 | include AutoJson 121 | field value, Int32 | Nil 122 | end 123 | 124 | private class JSONWithNilableUnion2 125 | include AutoJson 126 | field value, Int32? 127 | end 128 | 129 | describe "JSON mapping" do 130 | it "parses person" do 131 | person = JSONPerson.from_json(%({"name": "John", "age": 30})) 132 | person.should be_a(JSONPerson) 133 | person.name.should eq("John") 134 | person.age.should eq(30) 135 | end 136 | 137 | it "parses person without age" do 138 | person = JSONPerson.from_json(%({"name": "John"})) 139 | person.should be_a(JSONPerson) 140 | person.name.should eq("John") 141 | person.name.size.should eq(4) # This verifies that name is not nilable 142 | person.age.should be_nil 143 | end 144 | 145 | it "parses array of people" do 146 | people = Array(JSONPerson).from_json(%([{"name": "John"}, {"name": "Doe"}])) 147 | people.size.should eq(2) 148 | end 149 | 150 | it "does to_json" do 151 | person = JSONPerson.from_json(%({"name": "John", "age": 30})) 152 | person2 = JSONPerson.from_json(person.to_json) 153 | person2.name.should eq "John" 154 | person2.age.should eq 30 155 | end 156 | 157 | it "parses person with unknown attributes" do 158 | person = JSONPerson.from_json(%({"name": "John", "age": 30, "foo": "bar"})) 159 | person.should be_a(JSONPerson) 160 | person.name.should eq("John") 161 | person.age.should eq(30) 162 | end 163 | 164 | it "parses strict person with unknown attributes" do 165 | expect_raises JSON::ParseException, "unknown json attribute: foo" do 166 | StrictJSONPerson.from_json(%({"name": "John", "age": 30, "foo": "bar"})) 167 | end 168 | end 169 | 170 | it "raises if non-nilable attribute is nil" do 171 | expect_raises JSON::ParseException, "missing json attribute: name" do 172 | JSONPerson.from_json(%({"age": 30})) 173 | end 174 | end 175 | 176 | it "doesn't emit null by default when doing to_json" do 177 | person = JSONPerson.from_json(%({"name": "John"})) 178 | (person.to_json =~ /age/).should be_falsey 179 | end 180 | 181 | it "emits null on request when doing to_json" do 182 | person = JSONPersonEmittingNull.from_json(%({"name": "John"})) 183 | (person.to_json =~ /age/).should be_truthy 184 | end 185 | 186 | it "doesn't raises on false value when not-nil" do 187 | json = JSONWithBool.from_json(%({"value": false})) 188 | json.value.should be_false 189 | end 190 | 191 | it "parses json with Time::Format converter" do 192 | json = JSONWithTime.from_json(%({"value": "2014-10-31 23:37:16"})) 193 | json.value.should be_a(Time) 194 | json.value.to_s.should eq("2014-10-31 23:37:16") 195 | json.to_json.should eq(%({"value":"2014-10-31 23:37:16"})) 196 | end 197 | 198 | it "allows setting a nilable property to nil" do 199 | person = JSONPerson.new("John") 200 | person.age = 1 201 | person.age = nil 202 | end 203 | 204 | it "parses simple mapping" do 205 | person = JSONWithSimpleMapping.from_json(%({"name": "John", "age": 30})) 206 | person.should be_a(JSONWithSimpleMapping) 207 | person.name.should eq("John") 208 | person.age.should eq(30) 209 | end 210 | 211 | it "outputs with converter when nilable" do 212 | json = JSONWithNilableTime.new 213 | json.to_json.should eq("{}") 214 | end 215 | 216 | it "outputs with converter when nilable when emit_null is true" do 217 | json = JSONWithNilableTimeEmittingNull.new 218 | json.to_json.should eq(%({"value":null})) 219 | end 220 | 221 | it "parses json with keywords" do 222 | json = JSONWithKeywordsMapping.from_json(%({"end": 1, "abstract": 2})) 223 | json.end.should eq(1) 224 | json.abstract.should eq(2) 225 | end 226 | 227 | it "parses json with any" do 228 | json = JSONWithAny.from_json(%({"name": "Hi", "any": [{"x": 1}, 2, "hey", true, false, 1.5, null]})) 229 | json.name.should eq("Hi") 230 | json.any.raw.should eq([{"x" => 1}, 2, "hey", true, false, 1.5, nil]) 231 | json.to_json.should eq(%({"name":"Hi","any":[{"x":1},2,"hey",true,false,1.5,null]})) 232 | end 233 | 234 | it "parses json with problematic keys" do 235 | json = JsonWithProblematicKeys.from_json(%({"key": 1, "pull": 2})) 236 | json.key.should eq(1) 237 | json.pull.should eq(2) 238 | end 239 | 240 | it "parses json array as set" do 241 | json = JsonWithSet.from_json(%({"set": ["a", "a", "b"]})) 242 | json.set.should eq(Set(String){"a", "b"}) 243 | end 244 | 245 | it "allows small types of integer" do 246 | json = JSONWithSmallIntegers.from_json(%({"foo": 23, "bar": 7})) 247 | 248 | json.foo.should eq(23) 249 | typeof(json.foo).should eq(Int16) 250 | 251 | json.bar.should eq(7) 252 | typeof(json.bar).should eq(Int8) 253 | end 254 | 255 | describe "parses json with defaults" do 256 | it "mixed" do 257 | json = JsonWithDefaults.from_json(%({"a":1,"b":"bla"})) 258 | json.a.should eq 1 259 | json.b.should eq "bla" 260 | 261 | json = JsonWithDefaults.from_json(%({"a":1})) 262 | json.a.should eq 1 263 | json.b.should eq "Haha" 264 | 265 | json = JsonWithDefaults.from_json(%({"b":"bla"})) 266 | json.a.should eq 11 267 | json.b.should eq "bla" 268 | 269 | json = JsonWithDefaults.from_json(%({})) 270 | json.a.should eq 11 271 | json.b.should eq "Haha" 272 | 273 | json = JsonWithDefaults.from_json(%({"a":null,"b":null})) 274 | json.a.should eq 11 275 | json.b.should eq "Haha" 276 | end 277 | 278 | it "bool" do 279 | json = JsonWithDefaults.from_json(%({})) 280 | json.c.should eq true 281 | typeof(json.c).should eq Bool 282 | json.d.should eq false 283 | typeof(json.d).should eq Bool 284 | 285 | json = JsonWithDefaults.from_json(%({"c":false})) 286 | json.c.should eq false 287 | json = JsonWithDefaults.from_json(%({"c":true})) 288 | json.c.should eq true 289 | 290 | json = JsonWithDefaults.from_json(%({"d":false})) 291 | json.d.should eq false 292 | json = JsonWithDefaults.from_json(%({"d":true})) 293 | json.d.should eq true 294 | end 295 | 296 | it "with nilable" do 297 | json = JsonWithDefaults.from_json(%({})) 298 | 299 | json.e.should eq false 300 | typeof(json.e).should eq(Bool | Nil) 301 | 302 | json.f.should eq 1 303 | typeof(json.f).should eq(Int32 | Nil) 304 | 305 | json.g.should eq nil 306 | typeof(json.g).should eq(Int32 | Nil) 307 | 308 | json = JsonWithDefaults.from_json(%({"e":false})) 309 | json.e.should eq false 310 | json = JsonWithDefaults.from_json(%({"e":true})) 311 | json.e.should eq true 312 | end 313 | 314 | it "create new array every time" do 315 | json = JsonWithDefaults.from_json(%({})) 316 | json.h.should eq [1, 2, 3] 317 | json.h << 4 318 | json.h.should eq [1, 2, 3, 4] 319 | 320 | json = JsonWithDefaults.from_json(%({})) 321 | json.h.should eq [1, 2, 3] 322 | end 323 | end 324 | 325 | it "uses Time::EpochConverter" do 326 | string = %({"value":1459859781}) 327 | json = JSONWithTimeEpoch.from_json(string) 328 | json.value.should be_a(Time) 329 | json.value.should eq(Time.epoch(1459859781)) 330 | json.to_json.should eq(string) 331 | end 332 | 333 | it "uses Time::EpochMillisConverter" do 334 | string = %({"value":1459860483856}) 335 | json = JSONWithTimeEpochMillis.from_json(string) 336 | json.value.should be_a(Time) 337 | json.value.should eq(Time.epoch_ms(1459860483856)) 338 | json.to_json.should eq(string) 339 | end 340 | 341 | it "parses raw value from int" do 342 | string = %({"value":123456789123456789123456789123456789}) 343 | json = JSONWithRaw.from_json(string) 344 | json.value.should eq("123456789123456789123456789123456789") 345 | json.to_json.should eq(string) 346 | end 347 | 348 | it "parses raw value from float" do 349 | string = %({"value":123456789123456789.123456789123456789}) 350 | json = JSONWithRaw.from_json(string) 351 | json.value.should eq("123456789123456789.123456789123456789") 352 | json.to_json.should eq(string) 353 | end 354 | 355 | it "parses raw value from object" do 356 | string = %({"value":[null,true,false,{"x":[1,1.5]}]}) 357 | json = JSONWithRaw.from_json(string) 358 | json.value.should eq(%([null,true,false,{"x":[1,1.5]}])) 359 | json.to_json.should eq(string) 360 | end 361 | 362 | # it "parses with root" do 363 | # json = %({"result":{"heroes":[{"name":"Batman"}]}}) 364 | # result = JSONWithRoot.from_json(json) 365 | # result.result.should be_a(Array(JSONPerson)) 366 | # result.result.first.name.should eq "Batman" 367 | # result.to_json.should eq(json) 368 | # end 369 | 370 | # it "parses with nilable root" do 371 | # json = %({"result":null}) 372 | # result = JSONWithNilableRoot.from_json(json) 373 | # result.result.should be_nil 374 | # result.to_json.should eq("{}") 375 | # end 376 | 377 | # it "parses with nilable root and emit null" do 378 | # json = %({"result":null}) 379 | # result = JSONWithNilableRootEmitNull.from_json(json) 380 | # result.result.should be_nil 381 | # result.to_json.should eq(json) 382 | # end 383 | 384 | it "parses nilable union" do 385 | obj = JSONWithNilableUnion.from_json(%({"value": 1})) 386 | obj.value.should eq(1) 387 | obj.to_json.should eq(%({"value":1})) 388 | 389 | obj = JSONWithNilableUnion.from_json(%({"value": null})) 390 | obj.value.should be_nil 391 | obj.to_json.should eq(%({})) 392 | 393 | obj = JSONWithNilableUnion.from_json(%({})) 394 | obj.value.should be_nil 395 | obj.to_json.should eq(%({})) 396 | end 397 | 398 | it "parses nilable union2" do 399 | obj = JSONWithNilableUnion2.from_json(%({"value": 1})) 400 | obj.value.should eq(1) 401 | obj.to_json.should eq(%({"value":1})) 402 | 403 | obj = JSONWithNilableUnion2.from_json(%({"value": null})) 404 | obj.value.should be_nil 405 | obj.to_json.should eq(%({})) 406 | 407 | obj = JSONWithNilableUnion2.from_json(%({})) 408 | obj.value.should be_nil 409 | obj.to_json.should eq(%({})) 410 | end 411 | end 412 | -------------------------------------------------------------------------------- /spec/union_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct UnionTest 4 | include AutoJson 5 | 6 | field :a, Int32 | String 7 | end 8 | 9 | describe AutoJson do 10 | it { UnionTest.from_json(%q<{"a":1}>).a.should eq 1 } 11 | it { UnionTest.from_json(%q<{"a":"1"}>).a.should eq "1" } 12 | 13 | it { UnionTest.new(1).to_json.should eq %q<{"a":1}> } 14 | it { UnionTest.new("1").to_json.should eq %q<{"a":"1"}> } 15 | end 16 | -------------------------------------------------------------------------------- /spec/with_defaults_bool_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct WithDefaultsBoolTrue 4 | include AutoJson 5 | field :x, Bool, default: true 6 | end 7 | 8 | struct WithDefaultsBoolFalse 9 | include AutoJson 10 | field :x, Bool, default: false 11 | end 12 | 13 | describe "AutoJson" do 14 | context "WithDefaultsBoolTrue" do 15 | it { WithDefaultsBoolTrue.from_json(%q<{"x":true}>).x.should eq true } 16 | it { WithDefaultsBoolTrue.from_json(%q<{}>).x.should eq true } 17 | it { WithDefaultsBoolTrue.from_json(%q<{"x":null}>).x.should eq true } 18 | it { WithDefaultsBoolTrue.from_json(%q<{"x":false}>).x.should eq false } 19 | end 20 | 21 | context "WithDefaultsBoolFalse" do 22 | it { WithDefaultsBoolFalse.from_json(%q<{"x":false}>).x.should eq false } 23 | it { WithDefaultsBoolFalse.from_json(%q<{}>).x.should eq false } 24 | it { WithDefaultsBoolFalse.from_json(%q<{"x":true}>).x.should eq true } 25 | it { WithDefaultsBoolFalse.from_json(%q<{"x":null}>).x.should eq false } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/with_defaults_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct WithDefaultsSimple 4 | include AutoJson 5 | field :a, Int32 6 | end 7 | 8 | struct WithDefaults 9 | include AutoJson 10 | 11 | field :a, Int32, default: 11 12 | field :b, String, default: "def" 13 | field :c, WithDefaultsSimple 14 | end 15 | 16 | context "with_defaults" do 17 | it do 18 | s1 = WithDefaultsSimple.new(1) 19 | s2 = WithDefaults.new(10, "b", s1) 20 | s2.a.should eq 10 21 | s2.b.should eq "b" 22 | s2.c.should eq s1 23 | s2.to_json.should eq "{\"a\":10,\"b\":\"b\",\"c\":{\"a\":1}}" 24 | end 25 | 26 | it "load" do 27 | s2 = WithDefaults.from_json("{\"a\":10,\"b\":\"b\",\"c\":{\"a\":1}}") 28 | s2.a.should eq 10 29 | s2.b.should eq "b" 30 | s2.c.a.should eq 1 31 | end 32 | 33 | it "load" do 34 | s2 = WithDefaults.from_json("{\"a\":10,\"c\":{\"a\":1}}") 35 | s2.a.should eq 10 36 | s2.b.should eq "def" 37 | s2.c.a.should eq 1 38 | end 39 | 40 | it "load" do 41 | s2 = WithDefaults.from_json("{\"c\":{\"a\":1}}") 42 | s2.a.should eq 11 43 | s2.b.should eq "def" 44 | s2.c.a.should eq 1 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/with_nilable_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | struct WithNilableSimple 4 | include AutoJson 5 | field :a, Int32 6 | end 7 | 8 | struct WithNilable 9 | include AutoJson 10 | 11 | field :a, Int32 12 | field :b, String? 13 | field :d, Int32 | Nil 14 | field :c, WithNilableSimple 15 | field :e, Nil | Int32, emit_null: true 16 | end 17 | 18 | context "with_nilable" do 19 | it do 20 | s1 = WithNilableSimple.new(1) 21 | s2 = WithNilable.new(10, "b", nil, s1, 10) 22 | s2.a.should eq 10 23 | s2.b.should eq "b" 24 | s2.c.should eq s1 25 | s2.d.should eq nil 26 | s2.e.should eq 10 27 | s2.to_json.should eq "{\"a\":10,\"b\":\"b\",\"c\":{\"a\":1},\"e\":10}" 28 | end 29 | 30 | it "load" do 31 | s2 = WithNilable.from_json("{\"a\":10,\"b\":\"b\",\"d\":null,\"c\":{\"a\":1},\"e\":10}") 32 | s2.a.should eq 10 33 | s2.b.should eq "b" 34 | s2.c.a.should eq 1 35 | s2.d.should eq nil 36 | s2.e.should eq 10 37 | end 38 | 39 | it "load" do 40 | s2 = WithNilable.from_json("{\"a\":10,\"c\":{\"a\":1}}") 41 | s2.a.should eq 10 42 | s2.b.should eq nil 43 | s2.c.a.should eq 1 44 | s2.d.should eq nil 45 | s2.e.should eq nil 46 | end 47 | 48 | it "emit_null" do 49 | s1 = WithNilableSimple.new(1) 50 | s2 = WithNilable.new(a: 10, c: s1) 51 | s2.to_json.should eq "{\"a\":10,\"c\":{\"a\":1},\"e\":null}" 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /src/auto_json.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | require "auto_constructor" 3 | 4 | module AutoJson 5 | VERSION = "0.2" 6 | 7 | macro included 8 | include AutoConstructor 9 | 10 | {% if !@type.constant :JSON_OPTIONS %} 11 | JSON_OPTIONS = {} of Nil => Nil 12 | {% end %} 13 | 14 | macro json_options(**opts) 15 | \{% 16 | JSON_OPTIONS[:strict] = opts[:strict] 17 | JSON_OPTIONS[:emit_nulls] = opts[:emit_nulls] 18 | %} 19 | end 20 | 21 | macro finished 22 | def initialize(%pull : ::JSON::PullParser) 23 | \{% ready_fields = 0 %} 24 | \{% for field in AUTO_CONSTRUCTOR_FIELDS %} 25 | \{% if field[:json] != false && field[:serialize] != false %} 26 | \{% ready_fields = ready_fields + 1 %} 27 | \{% end %} 28 | _\{{field[:name].id}} = nil 29 | _found\{{field[:name].id}} = false 30 | \{% end %} 31 | 32 | %pull.read_object do |key| 33 | \{% if ready_fields > 0 %} 34 | case key 35 | \{% for field in AUTO_CONSTRUCTOR_FIELDS %} 36 | \{% if field[:json] != false && field[:serialize] != false %} 37 | when \{{(field[:json_key] || field[:key] || field[:name]).id.stringify}} 38 | _found\{{field[:name].id}} = true 39 | 40 | _\{{field[:name].id}} = 41 | \{% if field[:default] != nil %} %pull.read_null_or { \{% end %} 42 | \{% if field[:json_converter] || field[:converter] %} 43 | \{{field[:json_converter] || field[:converter]}}.from_json(%pull) 44 | \{% else %} 45 | (\{{field[:type].id}}).new(%pull) 46 | \{% end %} 47 | \{% if field[:default] != nil %} } \{% end %} 48 | \{% end %} 49 | \{% end %} 50 | else 51 | \{% if JSON_OPTIONS[:strict] %} 52 | raise JSON::ParseException.new("unknown json attribute: #{key}", 0, 0) 53 | \{% else %} 54 | %pull.skip 55 | \{% end %} 56 | end 57 | \{% else %} 58 | %pull.skip 59 | \{% end %} 60 | end 61 | 62 | \{% for field in AUTO_CONSTRUCTOR_FIELDS %} 63 | \{% if field[:default] == nil %} 64 | if _\{{field[:name].id}}.is_a?(Nil) && !_found\{{field[:name].id}} && !Union(\{{field[:type].id}}).nilable? 65 | raise JSON::ParseException.new("missing json attribute: #{\{{(field[:json_key] || field[:key] || field[:name]).id.stringify}}}", 0, 0) 66 | end 67 | \{% end %} 68 | \{% end %} 69 | 70 | \{% for field in AUTO_CONSTRUCTOR_FIELDS %} 71 | \{% if field[:nilable] %} 72 | \{% if field[:default] != nil %} 73 | @\{{field[:name].id}} = _found\{{field[:name].id}} ? _\{{field[:name].id}} : \{{field[:default]}} 74 | \{% else %} 75 | @\{{field[:name].id}} = _\{{field[:name].id}} 76 | \{% end %} 77 | \{% elsif field[:default] != nil %} 78 | @\{{field[:name].id}} = _\{{field[:name].id}}.is_a?(Nil) ? \{{field[:default]}} : _\{{field[:name].id}} 79 | \{% else %} 80 | @\{{field[:name].id}} = (_\{{field[:name].id}}).as(\{{field[:type].id}}) 81 | \{% end %} 82 | \{% end %} 83 | 84 | \{% for ai in AFTER_INITIALIZE %} \{{ ai.id }} \{% end %} 85 | end 86 | 87 | def to_json(json : JSON::Builder) 88 | json.object do 89 | \{% for field in AUTO_CONSTRUCTOR_FIELDS %} 90 | \{% if field[:json] != false && field[:serialize] != false %} 91 | _\{{field[:name].id}} = @\{{field[:name].id}} 92 | 93 | \{% unless (JSON_OPTIONS[:emit_nulls] || field[:json_emit_null] || field[:emit_null]) %} 94 | unless _\{{field[:name].id}}.is_a?(Nil) 95 | \{% end %} 96 | 97 | json.field(\{{(field[:json_key] || field[:key] || field[:name]).id.stringify}}) do 98 | \{% if field[:json_converter] || field[:converter] %} 99 | if _\{{field[:name].id}} 100 | \{{ field[:json_converter] || field[:converter] }}.to_json(_\{{field[:name].id}}, json) 101 | else 102 | nil.to_json(json) 103 | end 104 | \{% else %} 105 | _\{{field[:name].id}}.to_json(json) 106 | \{% end %} 107 | end 108 | 109 | \{{ (JSON_OPTIONS[:emit_nulls] || field[:json_emit_null] || field[:emit_null]) ? "".id : "end".id }} 110 | \{% end %} 111 | \{% end %} 112 | end 113 | end 114 | end 115 | 116 | def pack(io : IO) 117 | to_json(io) 118 | end 119 | 120 | def pack 121 | to_json 122 | end 123 | 124 | def self.from_json(io : IO) 125 | self.new(::JSON::PullParser.new(io)) 126 | end 127 | 128 | def self.unpack(io : IO) 129 | from_json(io) 130 | end 131 | 132 | def self.unpack(str : String) 133 | unpack IO::Memory.new(str) 134 | end 135 | end 136 | end 137 | --------------------------------------------------------------------------------