From 981b15da8eb68306ec642790a43c24883062df23 Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Mon, 24 Apr 2023 15:54:04 -0500 Subject: [PATCH 01/19] add json parser --- lib/decanter/parser/json_parser.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lib/decanter/parser/json_parser.rb diff --git a/lib/decanter/parser/json_parser.rb b/lib/decanter/parser/json_parser.rb new file mode 100644 index 0000000..9ff0003 --- /dev/null +++ b/lib/decanter/parser/json_parser.rb @@ -0,0 +1,14 @@ +module Decanter + module Parser + class JsonParser < ValueParser + + allow String + + parser do |val, options| + raise Decanter::ParseError.new 'Expects a JSON string' if val.is_a? String + next if (val.nil? || val === '') + JSON.parse(val) + end + end + end +end From b95004029b5f920fd25d55cbca28f34168ee6e23 Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Mon, 24 Apr 2023 17:33:56 -0500 Subject: [PATCH 02/19] include spec --- lib/decanter/parser/json_parser.rb | 2 +- spec/decanter/parser/json_parser_spec.rb | 26 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 spec/decanter/parser/json_parser_spec.rb diff --git a/lib/decanter/parser/json_parser.rb b/lib/decanter/parser/json_parser.rb index 9ff0003..4e6e6c3 100644 --- a/lib/decanter/parser/json_parser.rb +++ b/lib/decanter/parser/json_parser.rb @@ -5,7 +5,7 @@ class JsonParser < ValueParser allow String parser do |val, options| - raise Decanter::ParseError.new 'Expects a JSON string' if val.is_a? String + raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array next if (val.nil? || val === '') JSON.parse(val) end diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb new file mode 100644 index 0000000..f09d62b --- /dev/null +++ b/spec/decanter/parser/json_parser_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'JsonParser' do + + let(:name) { :foo } + + let(:parser) { Decanter::Parser::JsonParser } + + describe '#parse' do + it 'parses string value and returns a parsed JSON' do + expect(parser.parse(name, '{"key": "value"}')).to match({name => "{\"key\": \"value\"}"}) + end + + context 'with empty string' do + it 'returns nil' do + expect(parser.parse(name, '')).to match({name => nil}) + end + end + + context 'with nil' do + it 'returns nil' do + expect(parser.parse(name, nil)).to match({name => nil}) + end + end + end +end From b111d4fa3fd52340f59fbb7959f32d17f9b6ae2b Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Tue, 25 Apr 2023 11:58:12 -0500 Subject: [PATCH 03/19] add spec and force json parse on everything --- lib/decanter/parser/json_parser.rb | 4 +--- spec/decanter/parser/json_parser_spec.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/decanter/parser/json_parser.rb b/lib/decanter/parser/json_parser.rb index 4e6e6c3..51746ea 100644 --- a/lib/decanter/parser/json_parser.rb +++ b/lib/decanter/parser/json_parser.rb @@ -2,10 +2,8 @@ module Decanter module Parser class JsonParser < ValueParser - allow String - parser do |val, options| - raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array + raise Decanter::ParseError.new 'Expects a JSON string' if val.is_a?(Array) || val.is_a?(Hash) next if (val.nil? || val === '') JSON.parse(val) end diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index f09d62b..7857bcc 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -8,7 +8,7 @@ describe '#parse' do it 'parses string value and returns a parsed JSON' do - expect(parser.parse(name, '{"key": "value"}')).to match({name => "{\"key\": \"value\"}"}) + expect(parser.parse(name, '{"key": "value"}')).to match({name => {"key" => "value"}}) end context 'with empty string' do From 16006326f0d63a82d9ea23d481e65c7de0cb0f88 Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Tue, 25 Apr 2023 12:03:09 -0500 Subject: [PATCH 04/19] change version --- lib/decanter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/decanter/version.rb b/lib/decanter/version.rb index 98f9e75..764056d 100644 --- a/lib/decanter/version.rb +++ b/lib/decanter/version.rb @@ -1,3 +1,3 @@ module Decanter - VERSION = '4.0.1'.freeze + VERSION = '4.0.2'.freeze end From 2be0b967486f8d17477b018896d620e7cecadc00 Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Tue, 25 Apr 2023 12:10:02 -0500 Subject: [PATCH 05/19] update version in gemfile --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 117da4e..3d29efb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decanter (4.0.1) + decanter (4.0.2) actionpack (>= 4.2.10) activesupport rails-html-sanitizer (>= 1.0.4) From e0e564fee43f06fa51d2e8585e9a04b0a971ed71 Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Tue, 25 Apr 2023 12:11:55 -0500 Subject: [PATCH 06/19] fix version --- lib/decanter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/decanter/version.rb b/lib/decanter/version.rb index 764056d..98f9e75 100644 --- a/lib/decanter/version.rb +++ b/lib/decanter/version.rb @@ -1,3 +1,3 @@ module Decanter - VERSION = '4.0.2'.freeze + VERSION = '4.0.1'.freeze end From 21d8eb4617259872374157ab533595b8be9e0fa2 Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Tue, 25 Apr 2023 12:14:39 -0500 Subject: [PATCH 07/19] actually fix version --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3d29efb..117da4e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decanter (4.0.2) + decanter (4.0.1) actionpack (>= 4.2.10) activesupport rails-html-sanitizer (>= 1.0.4) From 11b9209b87ac431dec560621a4abd48ca9addc05 Mon Sep 17 00:00:00 2001 From: Daniel Schadd Date: Tue, 25 Apr 2023 12:20:24 -0500 Subject: [PATCH 08/19] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6b3a042..950da00 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ Decanter comes with the following parsers out of the box: - `:phone` - `:string` - `:array` +- `:json` Note: these parsers are designed to operate on a single value, except for `:array`. This parser expects an array, and will use the `parse_each` option to call a given parser on each of its elements: From 4442067822a96920b13043f33e65ba74203e9bc7 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 10:58:20 -0400 Subject: [PATCH 09/19] refactor check if val is a string --- lib/decanter/parser/json_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/decanter/parser/json_parser.rb b/lib/decanter/parser/json_parser.rb index 51746ea..d7fab7f 100644 --- a/lib/decanter/parser/json_parser.rb +++ b/lib/decanter/parser/json_parser.rb @@ -3,8 +3,8 @@ module Parser class JsonParser < ValueParser parser do |val, options| - raise Decanter::ParseError.new 'Expects a JSON string' if val.is_a?(Array) || val.is_a?(Hash) next if (val.nil? || val === '') + raise Decanter::ParseError.new 'Expects a JSON string' unless val.is_a?(String) JSON.parse(val) end end From 7efe8292b681eb547f0ba951368fc2228984ba76 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 11:09:52 -0400 Subject: [PATCH 10/19] Decanter::ParserError when invalid JSON is provided --- lib/decanter/parser/json_parser.rb | 12 ++++++++++-- spec/decanter/parser/json_parser_spec.rb | 6 ++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/decanter/parser/json_parser.rb b/lib/decanter/parser/json_parser.rb index d7fab7f..d7529a4 100644 --- a/lib/decanter/parser/json_parser.rb +++ b/lib/decanter/parser/json_parser.rb @@ -3,9 +3,17 @@ module Parser class JsonParser < ValueParser parser do |val, options| - next if (val.nil? || val === '') + next if val.blank? raise Decanter::ParseError.new 'Expects a JSON string' unless val.is_a?(String) - JSON.parse(val) + parse_json(val) + end + + def self.parse_json(val) + begin + JSON.parse(val) + rescue JSON::ParserError + raise Decanter::ParseError.new 'Invalid JSON string' + end end end end diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index 7857bcc..455921c 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -22,5 +22,11 @@ expect(parser.parse(name, nil)).to match({name => nil}) end end + + context 'when provided with an invalid JSON string' do + it 'raises a Decanter::ParseError' do + expect { parser.parse(name, 'invalid') }.to raise_error(Decanter::ParseError) + end + end end end From 996121811e68827276f3416f16af02c4083c6da3 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 11:14:00 -0400 Subject: [PATCH 11/19] add additional error test cases --- spec/decanter/parser/json_parser_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index 455921c..aaa4a3f 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -24,8 +24,12 @@ end context 'when provided with an invalid JSON string' do + json_parser_error = 'Invalid JSON string' it 'raises a Decanter::ParseError' do - expect { parser.parse(name, 'invalid') }.to raise_error(Decanter::ParseError) + expect { parser.parse(name, 'invalid') }.to raise_error(Decanter::ParseError, json_parser_error) + expect { parser.parse(name, '{ name: "John Smith", age: 30 }') }.to raise_error(Decanter::ParseError, json_parser_error) + expect { parser.parse(name, '{\"bio\": \"Line1 \n Line2\"}') }.to raise_error(Decanter::ParseError, json_parser_error) + expect { parser.parse(name, '{ "name": "John Smith", "age": 30, }') }.to raise_error(Decanter::ParseError, json_parser_error) end end end From 5e45710b10f40a58c41f8f2b936a9e25276e5cf5 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 11:15:47 -0400 Subject: [PATCH 12/19] add additional error test cases --- spec/decanter/parser/json_parser_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index aaa4a3f..e8078ca 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -23,7 +23,13 @@ end end - context 'when provided with an invalid JSON string' do + context 'with a number' do + it 'raises a Decanter::ParseError' do + expect { parser.parse(name, 1) }.to raise_error(Decanter::ParseError, 'Expects a JSON string') + end + end + + context 'with string that is invalid JSON' do json_parser_error = 'Invalid JSON string' it 'raises a Decanter::ParseError' do expect { parser.parse(name, 'invalid') }.to raise_error(Decanter::ParseError, json_parser_error) From ccc6873b10b40dde2fe7b6178d33bb033ff12920 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 11:20:09 -0400 Subject: [PATCH 13/19] rescue from all errors in JSON.parse --- lib/decanter/parser/json_parser.rb | 2 +- spec/decanter/parser/json_parser_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/decanter/parser/json_parser.rb b/lib/decanter/parser/json_parser.rb index d7529a4..7ab7927 100644 --- a/lib/decanter/parser/json_parser.rb +++ b/lib/decanter/parser/json_parser.rb @@ -11,7 +11,7 @@ class JsonParser < ValueParser def self.parse_json(val) begin JSON.parse(val) - rescue JSON::ParserError + rescue raise Decanter::ParseError.new 'Invalid JSON string' end end diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index e8078ca..f3351d9 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -23,9 +23,10 @@ end end - context 'with a number' do + context 'with a non-string value' do it 'raises a Decanter::ParseError' do expect { parser.parse(name, 1) }.to raise_error(Decanter::ParseError, 'Expects a JSON string') + expect { parser.parse(name, true) }.to raise_error(Decanter::ParseError, 'Expects a JSON string') end end From 95aa485407850e14cce0eae92ce74f67558e13fb Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 11:26:15 -0400 Subject: [PATCH 14/19] revert change to .blank? --- lib/decanter/parser/json_parser.rb | 2 +- spec/decanter/parser/json_parser_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/decanter/parser/json_parser.rb b/lib/decanter/parser/json_parser.rb index 7ab7927..ed7f050 100644 --- a/lib/decanter/parser/json_parser.rb +++ b/lib/decanter/parser/json_parser.rb @@ -3,7 +3,7 @@ module Parser class JsonParser < ValueParser parser do |val, options| - next if val.blank? + next if val.nil? || val === '' raise Decanter::ParseError.new 'Expects a JSON string' unless val.is_a?(String) parse_json(val) end diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index f3351d9..a21851f 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -27,6 +27,7 @@ it 'raises a Decanter::ParseError' do expect { parser.parse(name, 1) }.to raise_error(Decanter::ParseError, 'Expects a JSON string') expect { parser.parse(name, true) }.to raise_error(Decanter::ParseError, 'Expects a JSON string') + expect { parser.parse(name, {}) }.to raise_error(Decanter::ParseError, 'Expects a JSON string') end end From c6c23abd5f97477f5bb5cf055384a7661060c4c7 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 11:28:53 -0400 Subject: [PATCH 15/19] confirm both an object and an array within a JSON string can be parsed --- spec/decanter/parser/json_parser_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index a21851f..7c720c6 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -7,9 +7,13 @@ let(:parser) { Decanter::Parser::JsonParser } describe '#parse' do + + context 'with a valid JSON string' do it 'parses string value and returns a parsed JSON' do expect(parser.parse(name, '{"key": "value"}')).to match({name => {"key" => "value"}}) + expect(parser.parse(name, '["hello", "goodbye"]')).to match({name => ["hello", "goodbye"]}) end + end context 'with empty string' do it 'returns nil' do From 678d7871ba1277be311edd55932df1f1ce05c782 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 12:24:43 -0400 Subject: [PATCH 16/19] update readme --- README.md | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a733335..7c50a80 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,31 @@ gem 'decanter', '~> 5.0' ## Contents -- [Basic Usage](#basic-usage) - - [Decanters](#decanters) - - [Generators](#generators) - - [Decanting Collections](#decanting-collections) - - [Nested resources](#nested-resources) - - [Default parsers](#default-parsers) - - [Parser options](#parser-options) - - [Exceptions](#exceptions) -- [Advanced usage](#advanced-usage) - - [Custom parsers](#custom-parsers) - - [Squashing inputs](#squashing-inputs) - - [Chaining parsers](#chaining-parsers) - - [Requiring params](#requiring-params) - - [Global configuration](#global-configuration) -- [Contributing](#contributing) +- [Decanter](#decanter) + - [Migration Guides](#migration-guides) + - [Contents](#contents) + - [Basic Usage](#basic-usage) + - [Decanters](#decanters) + - [Generators](#generators) + - [Decanters](#decanters-1) + - [Parsers](#parsers) + - [Resources](#resources) + - [Decanting Collections](#decanting-collections) + - [Control Over Decanting Collections](#control-over-decanting-collections) + - [Nested resources](#nested-resources) + - [Default parsers](#default-parsers) + - [Parser options](#parser-options) + - [Exceptions](#exceptions) + - [Advanced Usage](#advanced-usage) + - [Custom Parsers](#custom-parsers) + - [Custom parser methods](#custom-parser-methods) + - [Custom parser base classes](#custom-parser-base-classes) + - [Squashing inputs](#squashing-inputs) + - [Chaining parsers](#chaining-parsers) + - [Requiring params](#requiring-params) + - [Default values](#default-values) + - [Global configuration](#global-configuration) + - [Contributing](#contributing) ## Basic Usage @@ -171,6 +181,11 @@ Note: these parsers are designed to operate on a single value, except for `:arra input :ids, :array, parse_each: :integer ``` +The `:json` parser may also accept an array, but the array must be provided as a single JSON-encoded string value: +``` +'["abc", "def"]' +``` + ### Parser options Some parsers can receive options that modify their behavior. These options are passed in as named arguments to `input`: From f96d6334700646e402eb5401d6e465f125e1a1af Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 12:25:06 -0400 Subject: [PATCH 17/19] update version --- lib/decanter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/decanter/version.rb b/lib/decanter/version.rb index 7a560e6..e12ba21 100644 --- a/lib/decanter/version.rb +++ b/lib/decanter/version.rb @@ -1,3 +1,3 @@ module Decanter - VERSION = '5.0.0'.freeze + VERSION = '5.1.0'.freeze end From 21787597d5b627f1f18b10ee0f7485a21e2ab2c0 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 12:27:53 -0400 Subject: [PATCH 18/19] typos in spec --- spec/decanter/parser/json_parser_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/decanter/parser/json_parser_spec.rb b/spec/decanter/parser/json_parser_spec.rb index 7c720c6..eb66c83 100644 --- a/spec/decanter/parser/json_parser_spec.rb +++ b/spec/decanter/parser/json_parser_spec.rb @@ -9,7 +9,7 @@ describe '#parse' do context 'with a valid JSON string' do - it 'parses string value and returns a parsed JSON' do + it 'parses the string value and returns a parsed JSON' do expect(parser.parse(name, '{"key": "value"}')).to match({name => {"key" => "value"}}) expect(parser.parse(name, '["hello", "goodbye"]')).to match({name => ["hello", "goodbye"]}) end @@ -35,7 +35,7 @@ end end - context 'with string that is invalid JSON' do + context 'with a string that is invalid JSON' do json_parser_error = 'Invalid JSON string' it 'raises a Decanter::ParseError' do expect { parser.parse(name, 'invalid') }.to raise_error(Decanter::ParseError, json_parser_error) From 2a7e4f45939f099344833497b76542297213c5d3 Mon Sep 17 00:00:00 2001 From: Nikki Dow Date: Fri, 22 Aug 2025 12:29:13 -0400 Subject: [PATCH 19/19] update gemfile.lock to fix CI build --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index b9f6b37..28775bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decanter (5.0.0) + decanter (5.1.0) actionpack (>= 7.1.3.2) activesupport rails (>= 7.1.3.2)