diff --git a/spec/std/yaml/mapping_spec.cr b/spec/std/yaml/mapping_spec.cr index 2bee92530218..81108679eadd 100644 --- a/spec/std/yaml/mapping_spec.cr +++ b/spec/std/yaml/mapping_spec.cr @@ -20,6 +20,13 @@ private class StrictYAMLPerson }, true) end +private class YAMLPersonWithExtras + YAML.mapping({ + name: {type: String}, + age: {type: Int32, nilable: true}, + }, extra: "extras") +end + private class YAMLWithBool YAML.mapping value: Bool end @@ -145,6 +152,15 @@ describe "YAML mapping" do end ex.location.should eq({3, 1}) end + it "parses person with extra attributes" do + person = YAMLPersonWithExtras.from_yaml("---\nname: John\nunknown: [1, 2, 3]\nage: 30\n") + person.should be_a(YAMLPersonWithExtras) + person.name.should eq("John") + person.age.should eq(30) + person.extras.should be_a(Hash(String, YAML::Any)) + person.extras["unknown"].should be_a(YAML::Any) + person.extras["unknown"].size.should eq(3) + end it "does to_yaml" do person = YAMLPerson.from_yaml("---\nname: John\nage: 30\n") diff --git a/src/json/mapping.cr b/src/json/mapping.cr index 68353789f8a2..a85c288b592f 100644 --- a/src/json/mapping.cr +++ b/src/json/mapping.cr @@ -60,7 +60,11 @@ module JSON # If *strict* is `true`, unknown properties in the JSON # document will raise a parse exception. The default is `false`, so unknown properties # are silently ignored. - macro mapping(properties, strict = false) + macro mapping(properties, strict = false, extra = nil) + {% if extra && properties.keys.includes? extra.id %} + {{ raise "Name for extra property already in use: #{extra.id}" }} + {% end %} + {% for key, value in properties %} {% properties[key] = {type: value} unless value.is_a?(HashLiteral) || value.is_a?(NamedTupleLiteral) %} {% end %} @@ -89,12 +93,24 @@ module JSON {% end %} {% end %} + {% if extra %} + @{{extra.id}} : Hash(String, ::JSON::Any) + + def {{extra.id}} + @{{extra.id}} + end + {% end %} + def initialize(%pull : ::JSON::PullParser) {% for key, value in properties %} %var{key.id} = nil %found{key.id} = false {% end %} + {% if extra %} + @{{extra.id}} = Hash(String, ::JSON::Any).new + {% end %} + %location = %pull.location %pull.read_begin_object while %pull.kind != :end_object @@ -130,6 +146,8 @@ module JSON else {% if strict %} raise ::JSON::ParseException.new("Unknown json attribute: #{key}", *%key_location) + {% elsif extra %} + @{{extra.id}}[key] = ::JSON::Any.new(%pull) {% else %} %pull.skip {% end %} diff --git a/src/yaml/mapping.cr b/src/yaml/mapping.cr index f02d0ebff1a0..f813aaf64932 100644 --- a/src/yaml/mapping.cr +++ b/src/yaml/mapping.cr @@ -67,7 +67,11 @@ module YAML # it and initializes this type's instance variables. # # This macro also declares instance variables of the types given in the mapping. - macro mapping(properties, strict = false) + macro mapping(properties, strict = false, extra = nil) + {% if extra && properties.keys.includes? extra.id %} + {{ raise "Name for extra property already in use: #{extra.id}" }} + {% end %} + {% for key, value in properties %} {% properties[key] = {type: value} unless value.is_a?(HashLiteral) || value.is_a?(NamedTupleLiteral) %} {% end %} @@ -96,12 +100,24 @@ module YAML {% end %} {% end %} + {% if extra %} + @{{extra.id}} : Hash(String, ::YAML::Any) + + def {{extra.id}} + @{{extra.id}} + end + {% end %} + def initialize(%pull : ::YAML::PullParser) {% for key, value in properties %} %var{key.id} = nil %found{key.id} = false {% end %} + {% if extra %} + @{{extra.id}} = Hash(String, ::YAML::Any).new + {% end %} + %mapping_location = %pull.location %pull.read_mapping_start @@ -129,6 +145,8 @@ module YAML else {% if strict %} raise ::YAML::ParseException.new("Unknown yaml attribute: #{key}", *%key_location) + {% elsif extra %} + @{{extra.id}}[key] = ::YAML::Any.new(%pull) {% else %} %pull.skip {% end %} @@ -182,6 +200,14 @@ module YAML {% end %} end {% end %} + {% if extra %} + _{{extra.id}} = @{{extra.id}} + + unless _{{extra.id}}.nil? + "{{extra.id}}".to_yaml(%yaml) + _{{extra.id}}.to_yaml(%yaml) + end + {% end %} end end end