From 7f0573e23193a6067a444e017f66ce414760081b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 8 Sep 2026 23:40:11 +1200 Subject: [PATCH 1/3] Separate JSON parse and generate options Assisted-By: devx/7ef83274-928f-499d-b668-48449b1ff60e --- lib/protocol/websocket/coder/json.rb | 14 ++++++++------ releases.md | 4 ++++ test/protocol/websocket/coder/json.rb | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 test/protocol/websocket/coder/json.rb diff --git a/lib/protocol/websocket/coder/json.rb b/lib/protocol/websocket/coder/json.rb index 7e6da6b..cd299ea 100644 --- a/lib/protocol/websocket/coder/json.rb +++ b/lib/protocol/websocket/coder/json.rb @@ -11,23 +11,25 @@ module Coder # A JSON coder that uses the standard JSON library. class JSON # Initialize a new JSON coder. - # @parameter options [Hash] Options to pass to the JSON library when parsing or generating. - def initialize(**options) - @options = options + # @parameter parse_options [Hash] Options to pass to the JSON library when parsing. + # @parameter generate_options [Hash] Options to pass to the JSON library when generating. + def initialize(parse_options: {}, generate_options: {}) + @parse_options = parse_options + @generate_options = generate_options end # Parse a JSON buffer into an object. def parse(buffer) - ::JSON.parse(buffer, **@options) + ::JSON.parse(buffer, **@parse_options) end # Generate a JSON buffer from an object. def generate(object) - ::JSON.generate(object, **@options) + ::JSON.generate(object, **@generate_options) end # The default JSON coder. This coder will symbolize names. - DEFAULT = new(symbolize_names: true) + DEFAULT = new(parse_options: {symbolize_names: true}) end end end diff --git a/releases.md b/releases.md index 7ebaeda..2d3df76 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Separate JSON parsing and generation options so parser-only settings such as `symbolize_names` do not break outgoing messages with JSON 3. + ## v0.21.1 - If `Connection#close_write` fails, the connection will now be fully closed to prevent hanging connections. diff --git a/test/protocol/websocket/coder/json.rb b/test/protocol/websocket/coder/json.rb new file mode 100644 index 0000000..aa6261a --- /dev/null +++ b/test/protocol/websocket/coder/json.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require "protocol/websocket/coder/json" + +describe Protocol::WebSocket::Coder::JSON do + let(:buffer) {'{"hello":"world"}'} + let(:object) {{"hello" => "world"}} + + it "applies parsing options only when parsing" do + coder = subject.new(parse_options: {symbolize_names: true}) + + expect(coder.parse(buffer)).to be == {hello: "world"} + expect(coder.generate(object)).to be == buffer + end + + it "applies generation options only when generating" do + coder = subject.new(generate_options: {space: " "}) + + expect(coder.parse(buffer)).to be == object + expect(coder.generate(object)).to be == '{"hello": "world"}' + end +end From f3f4eb24884fb3611888edaa94fb75125e3e97c2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 9 Sep 2026 00:00:03 +1200 Subject: [PATCH 2/3] Hard-code JSON parsing behavior Assisted-By: devx/7ef83274-928f-499d-b668-48449b1ff60e --- lib/protocol/websocket/coder/json.rb | 14 +++----------- releases.md | 2 +- test/protocol/websocket/coder/json.rb | 17 ++++++----------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/lib/protocol/websocket/coder/json.rb b/lib/protocol/websocket/coder/json.rb index cd299ea..0a23815 100644 --- a/lib/protocol/websocket/coder/json.rb +++ b/lib/protocol/websocket/coder/json.rb @@ -10,26 +10,18 @@ module WebSocket module Coder # A JSON coder that uses the standard JSON library. class JSON - # Initialize a new JSON coder. - # @parameter parse_options [Hash] Options to pass to the JSON library when parsing. - # @parameter generate_options [Hash] Options to pass to the JSON library when generating. - def initialize(parse_options: {}, generate_options: {}) - @parse_options = parse_options - @generate_options = generate_options - end - # Parse a JSON buffer into an object. def parse(buffer) - ::JSON.parse(buffer, **@parse_options) + ::JSON.parse(buffer, symbolize_names: true) end # Generate a JSON buffer from an object. def generate(object) - ::JSON.generate(object, **@generate_options) + ::JSON.generate(object) end # The default JSON coder. This coder will symbolize names. - DEFAULT = new(parse_options: {symbolize_names: true}) + DEFAULT = new end end end diff --git a/releases.md b/releases.md index 2d3df76..890517f 100644 --- a/releases.md +++ b/releases.md @@ -2,7 +2,7 @@ ## Unreleased - - Separate JSON parsing and generation options so parser-only settings such as `symbolize_names` do not break outgoing messages with JSON 3. + - Fix JSON 3 compatibility by applying `symbolize_names` only when parsing incoming messages. ## v0.21.1 diff --git a/test/protocol/websocket/coder/json.rb b/test/protocol/websocket/coder/json.rb index aa6261a..c3629a8 100644 --- a/test/protocol/websocket/coder/json.rb +++ b/test/protocol/websocket/coder/json.rb @@ -7,19 +7,14 @@ describe Protocol::WebSocket::Coder::JSON do let(:buffer) {'{"hello":"world"}'} - let(:object) {{"hello" => "world"}} + let(:object) {{hello: "world"}} + let(:coder) {subject.new} - it "applies parsing options only when parsing" do - coder = subject.new(parse_options: {symbolize_names: true}) - - expect(coder.parse(buffer)).to be == {hello: "world"} - expect(coder.generate(object)).to be == buffer + it "parses object keys as symbols" do + expect(coder.parse(buffer)).to be == object end - it "applies generation options only when generating" do - coder = subject.new(generate_options: {space: " "}) - - expect(coder.parse(buffer)).to be == object - expect(coder.generate(object)).to be == '{"hello": "world"}' + it "generates JSON" do + expect(coder.generate(object)).to be == buffer end end From 141be8773a81c84210aa3517386e5404ac3d3d06 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 9 Sep 2026 00:00:53 +1200 Subject: [PATCH 3/3] Use the JSON module as the default coder Assisted-By: devx/7ef83274-928f-499d-b668-48449b1ff60e --- lib/protocol/websocket/coder.rb | 2 +- lib/protocol/websocket/coder/json.rb | 9 +++------ releases.md | 2 +- test/protocol/websocket/coder/json.rb | 5 ++--- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/protocol/websocket/coder.rb b/lib/protocol/websocket/coder.rb index 5d4a1a7..582ca1d 100644 --- a/lib/protocol/websocket/coder.rb +++ b/lib/protocol/websocket/coder.rb @@ -10,7 +10,7 @@ module WebSocket # @namespace module Coder # The default coder for WebSocket messages. - DEFAULT = JSON::DEFAULT + DEFAULT = JSON end end end diff --git a/lib/protocol/websocket/coder/json.rb b/lib/protocol/websocket/coder/json.rb index 0a23815..89ba7fa 100644 --- a/lib/protocol/websocket/coder/json.rb +++ b/lib/protocol/websocket/coder/json.rb @@ -9,19 +9,16 @@ module Protocol module WebSocket module Coder # A JSON coder that uses the standard JSON library. - class JSON + module JSON # Parse a JSON buffer into an object. - def parse(buffer) + def self.parse(buffer) ::JSON.parse(buffer, symbolize_names: true) end # Generate a JSON buffer from an object. - def generate(object) + def self.generate(object) ::JSON.generate(object) end - - # The default JSON coder. This coder will symbolize names. - DEFAULT = new end end end diff --git a/releases.md b/releases.md index 890517f..6f246f8 100644 --- a/releases.md +++ b/releases.md @@ -2,7 +2,7 @@ ## Unreleased - - Fix JSON 3 compatibility by applying `symbolize_names` only when parsing incoming messages. + - Fix JSON 3 compatibility by applying `symbolize_names` only when parsing incoming messages. `Coder::JSON` is now a module with fixed parsing and generation behavior; pass a different coder to customize serialization. ## v0.21.1 diff --git a/test/protocol/websocket/coder/json.rb b/test/protocol/websocket/coder/json.rb index c3629a8..8106405 100644 --- a/test/protocol/websocket/coder/json.rb +++ b/test/protocol/websocket/coder/json.rb @@ -8,13 +8,12 @@ describe Protocol::WebSocket::Coder::JSON do let(:buffer) {'{"hello":"world"}'} let(:object) {{hello: "world"}} - let(:coder) {subject.new} it "parses object keys as symbols" do - expect(coder.parse(buffer)).to be == object + expect(subject.parse(buffer)).to be == object end it "generates JSON" do - expect(coder.generate(object)).to be == buffer + expect(subject.generate(object)).to be == buffer end end