{"record":{"id":"a4c1e4c3d8f8b989","repo":"BoundaryML/baml","slug":"failed-to-convert-ruby-object-to-json-errors-were-ruby","errorCode":null,"errorMessage":"failed to convert Ruby object to JSON, errors were:\n{}\nRuby object:\n{}","messagePattern":"failed to convert Ruby object to JSON, errors were:\n(.+?)\nRuby object:\n(.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"engine/language_client_ruby/ext/ruby_ffi/src/ruby_to_json.rs","lineNumber":228,"sourceCode":"    /// Convert a Ruby object to a JSON object.\n    ///\n    /// We have to implement this ourselves instead of relying on Serde, because in the codegen,\n    /// we can't convert a BAML-generated type to a hash trivially (specifically union-typed\n    /// fields do not serialize correctly, see https://sorbet.org/docs/tstruct#serialize-gotchas)\n    ///\n    /// We do still rely on :serialize for enums though.\n    pub fn convert(from: Value) -> crate::Result<BamlValue> {\n        let ruby = Ruby::get_with(from);\n        let result = RubyToJson { ruby: &ruby }.to_json(from, vec![]);\n\n        match result {\n            Ok(value) => Ok(value),\n            Err(e) => {\n                let mut errors = vec![];\n                for error in e {\n                    errors.push(format!(\"  {}: {}\", error.position.join(\".\"), error.message));\n                }\n                Err(Error::new(\n                    ruby.exception_type_error(),\n                    format!(\n                        \"failed to convert Ruby object to JSON, errors were:\\n{}\\nRuby object:\\n{}\",\n                        errors.join(\"\\n\"),\n                        from.inspect()\n                    ),\n                ))\n            }\n        }\n    }\n\n    pub fn convert_hash_to_json(from: RHash) -> crate::Result<IndexMap<String, BamlValue>> {\n        let ruby = Ruby::get_with(from);\n        let result = RubyToJson { ruby: &ruby }.hash_to_map(from, vec![]);\n\n        match result {\n            Ok(value) => Ok(value),\n            Err(e) => {","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/language_client_ruby/ext/ruby_ffi/src/ruby_to_json.rs#L210-L246","documentation":"A Ruby TypeError raised by RubyToJson::convert when a Ruby object passed across the FFI cannot be converted to JSON. It aggregates every field-level conversion error (position.path and message) plus the Ruby object's inspect() output. This is the entry-point variant used for whole Ruby values handed to BAML (e.g. function inputs).","triggerScenarios":"Passing a Ruby object as a BAML function argument (via parse_llm_response/invoke paths that call RubyToJson::convert) where some attribute is not JSON-serializable: symbols, BigDecimal without to_json, Time without a serializer, unexpected nested types, or objects not matching the declared parameter schema.","commonSituations":"Forgetting `require 'json'` or an ActiveModel serializer, passing ActiveRecord objects with non-serializable attributes (binary columns, symbols), passing a Ruby Struct/OpenStruct whose fields don't match the .baml parameter types, or passing core Ruby objects like Regexp/Range.","solutions":["Read the per-field error list (position.path: message) to find exactly which attributes failed.","Convert inputs to plain primitives: pass Hashes/Arrays/Strings/numbers/booleans/nil instead of custom objects.","For model objects, explicitly serialize: model.as_json or model.to_json with only JSON-safe attributes.","Add or fix custom to_json/#as_json for types like BigDecimal/Time (require 'json/add/...' or map to iso8601).","Verify argument names/types match the .baml function signature after regenerating the client."],"exampleFix":"// before\nb.ExtractResume(resume: ResumeRecord.new(raw: raw_blob))\n\n// after\nb.ExtractResume(resume: {\n  name: record.name,\n  skills: record.skills.map(&:to_s),\n  created_at: record.created_at.iso8601\n})","handlingStrategy":"validation","validationCode":"def baml_safe?(obj)\n  JSON.generate(obj)\n  true\nrescue JSON::GeneratorError, TypeError\n  false\nend\n# before calling:\nraise 'inputs not JSON-safe' unless baml_safe?(inputs)","typeGuard":"def json_primitive?(v)\n  v.nil? || v == true || v == false || v.is_a?(String) ||\n    v.is_a?(Integer) || v.is_a?(Float) ||\n    (v.is_a?(Array) && v.all? { |e| json_primitive?(e) }) ||\n    (v.is_a?(Hash) && v.values.all? { |e| json_primitive?(e) })\nend","tryCatchPattern":"begin\n  b.ExtractResume(resume: inputs)\nrescue TypeError => e\n  logger.error(\"BAML input conversion failed: #{e.message}\")\n  inputs = JSON.parse(inputs.to_json) # sanitize and retry\nend","preventionTips":["Pass plain Hashes/Arrays/primitives to BAML functions, not model objects.","Explicitly serialize Time/BigDecimal (iso8601, to_f) before passing.","Round-trip complex inputs through JSON.parse(obj.to_json) to sanitize.","Keep BAML parameter types and Ruby payloads in sync via codegen."],"tags":["ruby","json","serialization","ffi"],"backgroundTag":"json-serialization-failed","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}