diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_literal.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_literal.rs index 6a8629921..ccda9ab3b 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_literal.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_literal.rs @@ -7,6 +7,7 @@ use internal_baml_core::ir::FieldType; use crate::{ deserializer::{ coercer::{coerce_primitive::coerce_bool, match_string::match_string, TypeCoercer}, + deserialize_flags::{DeserializerConditions, Flag}, types::BamlValueWithFlags, }, jsonish, @@ -46,6 +47,22 @@ impl TypeCoercer for LiteralValue { Some(v) => v, }; + // If we get an object with a single key-value pair, try to extract the value + if let jsonish::Value::Object(obj) = value { + if obj.len() == 1 { + let (key, inner_value) = obj.iter().next().unwrap(); + // only extract value if it's a primitive (not an object or array, hoping to god its fixed) + match inner_value { + jsonish::Value::Number(_) | jsonish::Value::Boolean(_) | jsonish::Value::String(_) => { + let mut result = self.coerce(ctx, target, Some(inner_value))?; + result.add_flag(Flag::ObjectToPrimitive(jsonish::Value::Object(obj.clone()))); + return Ok(result); + } + _ => {} + } + } + } + match literal { LiteralValue::Int(literal_int) => { let BamlValueWithFlags::Int(coerced_int) = coerce_int(ctx, target, Some(value))? diff --git a/engine/baml-lib/jsonish/src/tests/test_literals.rs b/engine/baml-lib/jsonish/src/tests/test_literals.rs index e75361b18..5874b3ccb 100644 --- a/engine/baml-lib/jsonish/src/tests/test_literals.rs +++ b/engine/baml-lib/jsonish/src/tests/test_literals.rs @@ -209,3 +209,137 @@ test_deserializer!( ]), "TWO" ); + +test_deserializer!( + test_union_literal_with_multiple_types_from_object, + EMPTY_FILE, + r#"{ + "status": 1 +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]), + 1 +); + +// Test with integer value +test_deserializer!( + test_union_literal_with_multiple_types_from_object_int, + EMPTY_FILE, + r#"{ + "status": 1 +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]), + 1 +); + +// Test with boolean value +test_deserializer!( + test_union_literal_with_multiple_types_from_object_bool, + EMPTY_FILE, + r#"{ + "result": true +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]), + true +); + +// Test with string value +test_deserializer!( + test_union_literal_with_multiple_types_from_object_string, + EMPTY_FILE, + r#"{ + "value": "THREE" +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]), + "THREE" +); + +// Test with object that has multiple keys (should fail) +test_failing_deserializer!( + test_union_literal_with_multiple_types_from_multi_key_object, + EMPTY_FILE, + r#"{ + "status": 1, + "message": "success" +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]) +); + +// Test with nested object (should fail) +test_failing_deserializer!( + test_union_literal_with_multiple_types_from_nested_object, + EMPTY_FILE, + r#"{ + "status": { + "code": 1 + } +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]) +); + +// Test with quoted string value +test_deserializer!( + test_union_literal_with_multiple_types_from_object_quoted_string, + EMPTY_FILE, + r#"{ + "value": "\"THREE\"" +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]), + "THREE" +); + +// Test with string value and extra text +test_deserializer!( + test_union_literal_with_multiple_types_from_object_string_extra, + EMPTY_FILE, + r#"{ + "value": "The answer is THREE" +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]), + "THREE" +); + +// Test with array value (should fail) +test_failing_deserializer!( + test_union_literal_with_multiple_types_from_object_array, + EMPTY_FILE, + r#"{ + "values": [1] +}"#, + FieldType::Union(vec![ + FieldType::Literal(LiteralValue::Int(1)), + FieldType::Literal(LiteralValue::Bool(true)), + FieldType::Literal(LiteralValue::String("THREE".into())), + ]) +);