From 5b3f7d3f7f7e7cbebb6d2b535490f53377add641 Mon Sep 17 00:00:00 2001 From: Caspian Baska Date: Fri, 6 Nov 2020 12:31:22 +1100 Subject: [PATCH] Add Enum::StringConverter(T) --- spec/std/json/serializable_spec.cr | 20 +++++++++++++++++++ spec/std/yaml/serializable_spec.cr | 19 ++++++++++++++++++ src/json/from_json.cr | 10 ++++++++++ src/json/to_json.cr | 31 ++++++++++++++++++++++++++++++ src/yaml/from_yaml.cr | 9 +++++++++ src/yaml/to_yaml.cr | 6 ++++++ 6 files changed, 95 insertions(+) diff --git a/spec/std/json/serializable_spec.cr b/spec/std/json/serializable_spec.cr index cb533a0179a1..4fa4fa4bdea4 100644 --- a/spec/std/json/serializable_spec.cr +++ b/spec/std/json/serializable_spec.cr @@ -191,6 +191,18 @@ class JSONAttrWithSmallIntegers property bar : Int8 end +class JSONAttrWithEnumString + include JSON::Serializable + + enum Hint + One + Two + end + + @[YAML::Field(converter: Enum::StringConverter(JSONAttrWithEnumString::Hint))] + property hint : Hint +end + class JSONAttrWithTimeEpoch include JSON::Serializable @@ -683,6 +695,14 @@ describe "JSON mapping" do end end + it "uses Enum::StringConverter(T)" do + string = %({"hint":"One"}) + message = JSONAttrWithEnumString.from_json(string) + message.hint.should be_a(JSONAttrWithEnumString::Hint) + message.hint.should eq(JSONAttrWithEnumString::Hint::One) + message.to_json.should eq(string) + end + it "uses Time::EpochConverter" do string = %({"value":1459859781}) json = JSONAttrWithTimeEpoch.from_json(string) diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr index 6009d00b0880..23d1deddd085 100644 --- a/spec/std/yaml/serializable_spec.cr +++ b/spec/std/yaml/serializable_spec.cr @@ -204,6 +204,18 @@ class YAMLAttrWithSmallIntegers property bar : Int8 end +class YAMLAttrWithEnumString + include YAML::Serializable + + enum Hint + One + Two + end + + @[YAML::Field(converter: Enum::StringConverter(YAMLAttrWithEnumString::Hint))] + property hint : Hint +end + class YAMLAttrWithTimeEpoch include YAML::Serializable @@ -757,6 +769,13 @@ describe "YAML::Serializable" do end end + it "uses Enum::StringConverter(T)" do + message = YAMLAttrWithEnumString.from_yaml(%({"hint":"One"})) + message.hint.should be_a(YAMLAttrWithEnumString::Hint) + message.hint.should eq(YAMLAttrWithEnumString::Hint::One) + message.to_yaml.should eq("---\nhint: One\n") + end + it "uses Time::EpochConverter" do string = %({"value":1459859781}) yaml = YAMLAttrWithTimeEpoch.from_yaml(string) diff --git a/src/json/from_json.cr b/src/json/from_json.cr index a9903648c785..92f776009d96 100644 --- a/src/json/from_json.cr +++ b/src/json/from_json.cr @@ -370,3 +370,13 @@ module String::RawConverter value.read_raw end end + +module Enum::StringConverter(T) + def self.from_json(pull : JSON::PullParser) : T + if pull.kind.string? + T.parse(pull.read_string) + else + raise "Expecting string in JSON for #{T.class}, not #{pull.kind}" + end + end +end diff --git a/src/json/to_json.cr b/src/json/to_json.cr index daff774fdfb1..83f3a12f3d8e 100644 --- a/src/json/to_json.cr +++ b/src/json/to_json.cr @@ -309,3 +309,34 @@ module String::RawConverter json.raw(value) end end + +# Converter to be used with `JSON::Serializable` to write and read +# an `Enum` exclusively as a `String`. +# +# This is useful if you wish to serialise to `String`, instead of +# the default of a number representation of the `Enum`. +# +# ``` +# require "json" +# +# class EnumStringMessage +# include JSON::Serializable +# +# enum Hint +# Up +# Down +# end +# +# @[JSON::Field(converter: Enum::StringConverter(EnumStringMessage::Hint))] +# getter hint : Hint +# end +# +# message = EnumStringMessage.from_json(%({"hint":"Up"})) +# message.hint # => EnumStringMessage::Hint::Up +# message.to_json # => %({"hint":"Up"}) +# ``` +module Enum::StringConverter(T) + def self.to_json(value : T, json : JSON::Builder) + json.string(value.to_s) + end +end diff --git a/src/yaml/from_yaml.cr b/src/yaml/from_yaml.cr index 12fa47ba64b2..b8f7492a321a 100644 --- a/src/yaml/from_yaml.cr +++ b/src/yaml/from_yaml.cr @@ -322,6 +322,15 @@ module YAML::ArrayConverter(Converter) end end +module Enum::StringConverter(T) + def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : T + unless node.is_a?(YAML::Nodes::Scalar) + node.raise "Expected scalar, not #{node.class}" + end + T.parse(node.value) + end +end + struct Slice def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) {% if T != UInt8 %} diff --git a/src/yaml/to_yaml.cr b/src/yaml/to_yaml.cr index 726bbb765b92..495391e339f3 100644 --- a/src/yaml/to_yaml.cr +++ b/src/yaml/to_yaml.cr @@ -178,6 +178,12 @@ module YAML::ArrayConverter(Converter) end end +module Enum::StringConverter(T) + def self.to_yaml(value : T, yaml : YAML::Nodes::Builder) + yaml.scalar value.to_s + end +end + struct Slice def to_yaml(yaml : YAML::Nodes::Builder) {% if T != UInt8 %}