Skip to content

Commit 6bc8277

Browse files
dbrattliclaude
andcommitted
feat: Add Json static class with Fable-aware serialization
Refactor JSON serialization to use a static `Json` class with method overloads instead of standalone functions. This provides a cleaner API with: - `Json.dumps(obj)` - basic serialization with fableDefault - `Json.dumps(obj, indent)` - with indentation - `Json.dumps(obj, separators, ensureAscii)` - with custom formatting - `Json.dump(obj, fp)` - file serialization - `Json.loads(s)` / `Json.load(fp)` - deserialization (returns native Python types) The fableDefault handler automatically converts Fable numeric types (Int8-64, UInt8-64, Float32/64), unions, and records to JSON-serializable Python types. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 19f60be commit 6bc8277

File tree

2 files changed

+101
-27
lines changed

2 files changed

+101
-27
lines changed

src/stdlib/Json.fs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ type IExports =
2323
/// See https://docs.python.org/3/library/json.html#json.dumps
2424
[<NamedParams(fromIndex = 1)>]
2525
abstract dumps: obj: obj * indent: int * ``default``: (obj -> obj) -> string
26+
/// Serialize obj to a JSON formatted string with separators, ensure_ascii, and custom default
27+
/// See https://docs.python.org/3/library/json.html#json.dumps
28+
[<NamedParams(fromIndex = 1)>]
29+
abstract dumps:
30+
obj: obj * separators: string array * ensure_ascii: bool * ``default``: (obj -> obj) -> string
31+
/// Serialize obj to a JSON formatted string with indent, separators, ensure_ascii, and custom default
32+
/// See https://docs.python.org/3/library/json.html#json.dumps
33+
[<NamedParams(fromIndex = 1)>]
34+
abstract dumps:
35+
obj: obj * indent: int * separators: string array * ensure_ascii: bool * ``default``: (obj -> obj) -> string
2636
/// Deserialize a JSON document from a string to a Python object
2737
/// See https://docs.python.org/3/library/json.html#json.loads
2838
abstract loads: s: string -> obj
@@ -106,15 +116,38 @@ let fableDefault (o: obj) : obj =
106116
else
107117
raiseTypeError o
108118

109-
/// Serialize obj to JSON, automatically handling Fable types
110-
let dumps (obj: obj) : string = json.dumps (obj, ``default`` = fableDefault)
119+
/// Fable-aware JSON serialization with proper overloads.
120+
/// Automatically handles Fable types (Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64, unions, records).
121+
[<Erase>]
122+
type Json =
123+
/// Serialize obj to JSON, automatically handling Fable types
124+
static member inline dumps(obj: obj) : string =
125+
json.dumps (obj, ``default`` = fableDefault)
126+
127+
/// Serialize obj to JSON with indentation, automatically handling Fable types
128+
static member inline dumps(obj: obj, indent: int) : string =
129+
json.dumps (obj, indent, ``default`` = fableDefault)
111130

112-
/// Serialize obj to JSON with indentation, automatically handling Fable types
113-
let dumpsIndented (obj: obj) (indent: int) : string = json.dumps (obj, indent, ``default`` = fableDefault)
131+
/// Serialize obj to JSON with custom separators and ensure_ascii, automatically handling Fable types
132+
static member inline dumps(obj: obj, separators: string array, ensureAscii: bool) : string =
133+
json.dumps (obj, separators = separators, ensure_ascii = ensureAscii, ``default`` = fableDefault)
114134

115-
/// Serialize obj as JSON stream to file, automatically handling Fable types
116-
let dump (obj: obj) (fp: TextIOWrapper) : unit = json.dump (obj, fp, ``default`` = fableDefault)
135+
/// Serialize obj to JSON with indentation, custom separators, and ensure_ascii, automatically handling Fable types
136+
static member inline dumps(obj: obj, indent: int, separators: string array, ensureAscii: bool) : string =
137+
json.dumps (obj, indent = indent, separators = separators, ensure_ascii = ensureAscii, ``default`` = fableDefault)
117138

118-
/// Serialize obj as JSON stream to file with indentation, automatically handling Fable types
119-
let dumpIndented (obj: obj) (fp: TextIOWrapper) (indent: int) : unit =
120-
json.dump (obj, fp, indent, ``default`` = fableDefault)
139+
/// Serialize obj as JSON stream to file, automatically handling Fable types
140+
static member inline dump(obj: obj, fp: TextIOWrapper) : unit =
141+
json.dump (obj, fp, ``default`` = fableDefault)
142+
143+
/// Serialize obj as JSON stream to file with indentation, automatically handling Fable types
144+
static member inline dump(obj: obj, fp: TextIOWrapper, indent: int) : unit =
145+
json.dump (obj, fp, indent, ``default`` = fableDefault)
146+
147+
/// Deserialize a JSON document from a string to a Python object
148+
static member inline loads(s: string) : obj =
149+
json.loads s
150+
151+
/// Deserialize a JSON document from a file-like object to a Python object
152+
static member inline load(fp: TextIOWrapper) : obj =
153+
json.load fp

test/TestJson.fs

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,94 @@ module Fable.Python.Tests.Json
33
open Util.Testing
44
open Fable.Python.Json
55

6-
[<Fact>]
7-
let ``test works`` () =
8-
let result = true
9-
result |> equal true
6+
// Test types for union and record serialization
7+
type SimpleUnion =
8+
| CaseA
9+
| CaseB of int
10+
| CaseC of string * int
11+
12+
type SimpleRecord = { Name: string; Age: int }
1013

1114
[<Fact>]
12-
let ``test json dumps works`` () =
15+
let ``test Json.dumps with anonymous record works`` () =
1316
let object = {| A = 10; B = 20 |}
14-
let result = dumps object
17+
let result = Json.dumps object
1518
result |> equal """{"A": 10, "B": 20}"""
1619

1720
[<Fact>]
18-
let ``test json loads works`` () =
21+
let ``test Json.loads works`` () =
1922
let input = """{"Foo": 10, "Bar": "test"}"""
2023
let object = {| Foo = 10; Bar = "test" |}
21-
let result: {| Foo: int; Bar: string |} = unbox (json.loads input)
24+
let result: {| Foo: int; Bar: string |} = unbox (Json.loads input)
2225
result |> equal object
2326

2427
[<Fact>]
25-
let ``test json.dumps with nativeint works`` () =
28+
let ``test Json.dumps with int works`` () =
29+
let value: int = 42
30+
let result = Json.dumps value
31+
result |> equal "42"
32+
33+
[<Fact>]
34+
let ``test Json.dumps with nativeint works`` () =
2635
let value: nativeint = 42n
27-
let result = json.dumps value
36+
let result = Json.dumps value
2837
result |> equal "42"
2938

3039
[<Fact>]
31-
let ``test json.dumps with ResizeArray of nativeint works`` () =
40+
let ``test Json.dumps with ResizeArray works`` () =
3241
let values = ResizeArray([ 1n; 2n; 3n ])
33-
let result = json.dumps values
42+
let result = Json.dumps values
43+
result |> equal "[1, 2, 3]"
44+
45+
[<Fact>]
46+
let ``test Json.dumps with ResizeArray of int works`` () =
47+
let values = ResizeArray([ 1; 2; 3 ])
48+
let result = Json.dumps values
3449
result |> equal "[1, 2, 3]"
3550

3651
[<Fact>]
37-
let ``test json.dumps with nested object works`` () =
52+
let ``test Json.dumps with nested object works`` () =
3853
let obj =
3954
{| Name = "test"
4055
Values = ResizeArray([ 1n; 2n; 3n ]) |}
4156

42-
let result = dumps obj
57+
let result = Json.dumps obj
4358
result |> equal """{"Name": "test", "Values": [1, 2, 3]}"""
4459

4560
[<Fact>]
46-
let ``test json.loads with array works`` () =
61+
let ``test Json.loads with array works`` () =
4762
let input = "[1, 2, 3]"
48-
let result: int array = unbox (json.loads input)
63+
let result: int array = unbox (Json.loads input)
4964
result.Length |> equal 3
5065

5166
[<Fact>]
52-
let ``test json.dumps with indent works`` () =
67+
let ``test Json.dumps with indent works`` () =
5368
let obj = {| A = 1n |}
54-
let result = dumpsIndented obj 2
69+
let result = Json.dumps(obj, indent = 2)
5570
result.Contains("\n") |> equal true
71+
72+
[<Fact>]
73+
let ``test Json.dumps with record works`` () =
74+
let record = { Name = "Alice"; Age = 30 }
75+
let result = Json.dumps record
76+
// Note: Fable records use lowercase slot names
77+
result |> equal """{"name": "Alice", "age": 30}"""
78+
79+
[<Fact>]
80+
let ``test Json.dumps with simple union case works`` () =
81+
let union = CaseA
82+
let result = Json.dumps union
83+
// Note: Union cases without fields serialize as just the case name string
84+
result |> equal "\"CaseA\""
85+
86+
[<Fact>]
87+
let ``test Json.dumps with union case with single field works`` () =
88+
let union = CaseB 42
89+
let result = Json.dumps union
90+
result |> equal """["CaseB", 42]"""
91+
92+
[<Fact>]
93+
let ``test Json.dumps with union case with multiple fields works`` () =
94+
let union = CaseC("hello", 123)
95+
let result = Json.dumps union
96+
result |> equal """["CaseC", "hello", 123]"""

0 commit comments

Comments
 (0)