bblimke/webmock · error · WebMock::Util::Parsers::ParseError

Invalid JSON string: #{yaml}, Error: #{e.inspect}

Error message

Invalid JSON string: #{yaml}, Error: #{e.inspect}

What it means

WebMock::ParseError raised when WebMock's internal JSON parser (Util::Parsers::JSON, which converts JSON to YAML and calls YAML.load) cannot parse a body during request/stub body normalization. It fires when either the stub body pattern or the real request body is unparseable; the message echoes the offending string plus the underlying ArgumentError or Psych::SyntaxError. Because parsing routes through YAML, even valid JSON can fail when YAML misreads a scalar (classic case: unquoted date-like strings).

Source

Thrown at lib/webmock/util/parsers/json.rb:20

# This is a copy of https://github.com/jnunemaker/crack/blob/master/lib/crack/json.rb
# with date parsing removed
# Copyright (c) 2004-2008 David Heinemeier Hansson
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require_relative "parse_error"

module WebMock
  module Util
    module Parsers
      class JSON
        def self.parse(json)
          yaml = unescape(convert_json_to_yaml(json))
          YAML.load(yaml)
        rescue ArgumentError, Psych::SyntaxError => e
          raise ParseError, "Invalid JSON string: #{yaml}, Error: #{e.inspect}"
        end

        protected

        def self.unescape(str)
          str.gsub(/\\u([0-9a-f]{4})/) { [$1.hex].pack("U") }
        end

        # Ensure that ":" and "," are always followed by a space
        def self.convert_json_to_yaml(json) #:nodoc:
          scanner, quoting, marks, times = StringScanner.new(json), false, [], []
          while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
            case char = scanner[1]
            when '"', "'"
              if !quoting
                quoting = char
              elsif quoting == char
                quoting = false

View on GitHub (pinned to b187df8827)

Solutions

  1. Copy the body shown in the error message and run it through WebMock::Util::JSON.parse or JSON.parse in a console to see the exact cause
  2. Fix the malformed JSON: remove trailing commas, double-quote keys and strings, drop JavaScript-only literals (NaN, undefined, single quotes)
  3. If YAML scalar coercion is the culprit, match the raw body string instead of a parsed hash: .with(body: '{"d":"2026-08-22"}')
  4. For exotic bodies use a block matcher: .with(headers: { 'Content-Type' => 'application/json' }) { |req| JSON.parse(req.body)['d'] == '2026-08-22' }
  5. Upgrade webmock; parser edge cases (dates, unicode) keep getting fixed across releases

Example fix

// before
stub_request(:post, 'www.example.com')
  .with(body: { 'd' => '2026-08-22' }, headers: { 'Content-Type' => 'application/json' })
# date-like scalar can break the YAML-backed parser on some psych versions

// after - match the raw JSON string exactly
stub_request(:post, 'www.example.com')
  .with(body: '{"d":"2026-08-22"}', headers: { 'Content-Type' => 'application/json' })
Defensive patterns

Strategy: validation

Validate before calling

require 'json'

def valid_json_body?(body)
  JSON.parse(body)
  true
rescue JSON::ParserError
  false
end

body = client.build_payload
raise ArgumentError, 'client produced invalid JSON' unless valid_json_body?(body)
HTTP.post(url, body: body, headers: { 'Content-Type' => 'application/json' })

Type guard

def stubbable_json_pattern?(pattern)
  return true unless pattern.is_a?(String)
  return true unless pattern.strip.start_with?('{', '[')
  valid_json_body?(pattern)
end

Try / catch

begin
  WebMock::Util::JSON.parse(body)
rescue WebMock::ParseError => e
  # e.message shows the converted YAML plus the Psych/ArgumentError cause;
  # fix the payload or fixture - do not swallow this in tests
end

Prevention

When it happens

Trigger: stub_request(:post, url).with(body: {...}, headers: { 'Content-Type' => 'application/json' }) where either side contains invalid JSON: trailing commas, single-quoted strings, unquoted keys, NaN/Infinity; a JSON string body pattern like .with(body: '{"a":1,}'); YAML date-coercion failures on values such as 2026-08-22 under psych versions that turn them into Date objects; strings Psych rejects after the json-to-yaml conversion.

Common situations: Hand-written JSON fixtures with trailing commas; payloads assembled by string interpolation instead of a JSON serializer; Ruby or psych upgrades making YAML.load stricter or changing date coercion; non-UTF-8 bytes or a BOM in the body; tests that stub with a Hash while the client sends malformed JSON.

Understand the failure class

Related errors


AI-assisted analysis of bblimke/webmock@b187df8827 (2026-08-23). Data as JSON: /api/errors/25e4cf8f1952bfce. Report an issue: GitHub.