@@ -6,7 +6,6 @@ namespace FSharp.Data.GraphQL.Client
66open System
77open System.Collections .Generic
88open System.IO
9- open System.Reflection
109open System.Text
1110open System.Text .Json
1211open FSharp.Data .GraphQL
@@ -202,8 +201,24 @@ module private SchemaParser =
202201
203202module Serialization =
204203
205- let private isoDateFormat = " yyyy-MM-dd"
206- let private isoDateTimeFormat = " O"
204+ /// The default JSON serializer options used for request serialization when no custom options are provided.
205+ let defaultSerializerOptions =
206+ lazy ( FSharp.Data.GraphQL.Shared.Json.getSerializerOptions Seq.empty)
207+
208+ /// Converts special types (Uri, Upload, etc.) that System.Text.Json cannot handle natively
209+ /// into their JSON-serializable representations. Applied recursively to variable values.
210+ /// Also normalizes dictionary keys to camelCase to match GraphQL field naming conventions.
211+ let rec private normalizeForSerialization ( value : obj ) : obj =
212+ match value with
213+ | null -> null
214+ | :? string -> value // Must come before EnumerableValue: string implements IEnumerable
215+ | :? Uri as u -> box ( u.ToString ())
216+ | :? Upload as u -> box u.Name // File variables are written as the form-part name string
217+ | :? IDictionary< string, obj> as d ->
218+ // Apply FirstCharLower to keys: RecordBase.ToDictionary() uses PascalCase (FirstCharUpper) for property names
219+ d |> Seq.map ( fun kvp -> kvp.Key.FirstCharLower (), normalizeForSerialization kvp.Value) |> dict |> box
220+ | EnumerableValue items -> items |> Array.map normalizeForSerialization |> box
221+ | v -> v
207222
208223 /// Converts a JsonElement to an F# object recursively.
209224 let rec private deserializeElement ( element : JsonElement ) : obj =
@@ -236,55 +251,11 @@ module Serialization =
236251 |> Array.map ( fun ( name , element ) -> name, deserializeElement element)
237252 |> Map.ofArray)
238253
239- let private writeValue ( writer : Utf8JsonWriter ) =
240- let rec write ( value : obj ) =
241- match value with
242- | null -> writer.WriteNullValue ()
243- | OptionValue None -> writer.WriteNullValue ()
244- | OptionValue ( Some v) -> write v
245- | :? bool as b -> writer.WriteBooleanValue b
246- | :? int as n -> writer.WriteNumberValue n
247- | :? float as f -> writer.WriteNumberValue f
248- | :? decimal as d -> writer.WriteNumberValue d
249- | :? int64 as n -> writer.WriteNumberValue n
250- | :? uint64 as n -> writer.WriteNumberValue n
251- | :? int16 as n -> writer.WriteNumberValue ( int n)
252- | :? uint16 as n -> writer.WriteNumberValue ( uint32 n)
253- | :? byte as n -> writer.WriteNumberValue ( uint32 n)
254- | :? sbyte as n -> writer.WriteNumberValue ( int n)
255- | :? string as s -> writer.WriteStringValue s
256- | :? Guid as g -> writer.WriteStringValue ( g.ToString ())
257- | :? DateTime as d when d.Date = d -> writer.WriteStringValue ( d.ToString isoDateFormat)
258- | :? DateTime as d -> writer.WriteStringValue ( d.ToString isoDateTimeFormat)
259- | :? DateTimeOffset as d -> writer.WriteStringValue ( d.ToString isoDateTimeFormat)
260- | :? Uri as u -> writer.WriteStringValue ( u.ToString ())
261- | :? Upload as u -> writer.WriteStringValue u.Name
262- | :? IDictionary< string, obj> as dict ->
263- writer.WriteStartObject ()
264- for kvp in dict do
265- writer.WritePropertyName ( kvp.Key.FirstCharLower ())
266- write kvp.Value
267- writer.WriteEndObject ()
268- | EnumerableValue items ->
269- writer.WriteStartArray ()
270- Array.iter write items
271- writer.WriteEndArray ()
272- | EnumValue s -> writer.WriteStringValue s
273- | _ ->
274- let props = value.GetType() .GetProperties ( BindingFlags.Public ||| BindingFlags.Instance)
275- writer.WriteStartObject ()
276- for p in props do
277- writer.WritePropertyName ( p.Name.FirstCharLower ())
278- write ( p.GetValue value)
279- writer.WriteEndObject ()
280- write
281-
282254 /// Builds the JSON body for a standard GraphQL request.
283- let buildRequestJson ( operationName : string option ) ( query : string ) ( variables : ( string * obj ) []) =
255+ let buildRequestJson ( options : JsonSerializerOptions ) ( operationName : string option ) ( query : string ) ( variables : ( string * obj ) []) =
284256 Tracer.runAndMeasureExecutionTime " Built GraphQL request JSON" ( fun _ ->
285257 use stream = new MemoryStream ()
286258 use writer = new Utf8JsonWriter ( stream, JsonWriterOptions ( Indented = false ))
287- let write = writeValue writer
288259 writer.WriteStartObject ()
289260 writer.WritePropertyName " operationName"
290261 match operationName with
@@ -296,11 +267,8 @@ module Serialization =
296267 if variables = null || variables.Length = 0 then
297268 writer.WriteNullValue ()
298269 else
299- writer.WriteStartObject ()
300- for ( name, value) in variables do
301- writer.WritePropertyName name
302- write value
303- writer.WriteEndObject ()
270+ let dict = variables |> Array.map ( fun ( k , v ) -> k, normalizeForSerialization v) |> dict
271+ JsonSerializer.Serialize ( writer, dict, options)
304272 writer.WriteEndObject ()
305273 writer.Flush ()
306274 Encoding.UTF8.GetString ( stream.ToArray ()))
0 commit comments