diff --git a/packages/Thoth.Json.Core/CHANGELOG.md b/packages/Thoth.Json.Core/CHANGELOG.md index c680a45..54c88f7 100644 --- a/packages/Thoth.Json.Core/CHANGELOG.md +++ b/packages/Thoth.Json.Core/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +* Rework encoder API to not need a custom DU ([GH-188](https://github.com/thoth-org/Thoth.Json/pull/188/)) + ## 0.2.1 - 2023-12-12 ### Fixed diff --git a/packages/Thoth.Json.Core/Encode.fs b/packages/Thoth.Json.Core/Encode.fs index 47d878b..cc41439 100644 --- a/packages/Thoth.Json.Core/Encode.fs +++ b/packages/Thoth.Json.Core/Encode.fs @@ -7,24 +7,76 @@ open System [] module Encode = - let inline string value = Json.String value - let inline char value = Json.Char value + let inline string value = + { new IEncodable with + member _.Encode(helpers) = helpers.encodeString value + } + + let inline char value = + { new IEncodable with + member _.Encode(helpers) = helpers.encodeChar value + } + let inline guid value = value.ToString() |> string - let inline float value = Json.DecimalNumber value - let float32 (value: float32) = - Json.DecimalNumber(Operators.float value) + let inline float value = + { new IEncodable with + member _.Encode(helpers) = helpers.encodeDecimalNumber value + } + + let float32 (value: float32) = float (Operators.float value) let inline decimal (value: decimal) = value.ToString(CultureInfo.InvariantCulture) |> string - let inline nil<'T> = Json.Null - let inline bool value = Json.Boolean value - let inline object values = Json.Object values - let inline array values = Json.Array values - let list values = Json.Array(values |> List.toArray) - let seq values = Json.Array(values |> Seq.toArray) - let dict (values: Map) : Json = values |> Map.toList |> object + let inline nil<'T> = + { new IEncodable with + member _.Encode(helpers) = helpers.encodeNull () + } + + let inline bool value = + { new IEncodable with + member _.Encode(helpers) = helpers.encodeBool value + } + + let inline object (values: seq) = + { new IEncodable with + member _.Encode(helpers) = + let o = helpers.createEmptyObject () + + for k, v in values do + let ve = v.Encode(helpers) + helpers.setPropertyOnObject (o, k, ve) + + o + } + + let inline array (values: IEncodable array) = + { new IEncodable with + member _.Encode(helpers) = + values + |> Array.map (fun v -> v.Encode(helpers)) + |> helpers.encodeArray + } + + let list (values: IEncodable list) = + { new IEncodable with + member _.Encode(helpers) = + values + |> List.map (fun v -> v.Encode(helpers)) + |> helpers.encodeList + } + + let seq (values: IEncodable seq) = + { new IEncodable with + member _.Encode(helpers) = + values + |> Seq.map (fun v -> v.Encode(helpers)) + |> helpers.encodeSeq + } + + let dict (values: Map) : IEncodable = + values |> Map.toSeq |> object let inline bigint (value: bigint) = value.ToString() |> string @@ -36,12 +88,40 @@ module Encode = let inline datetime (value: DateTime) = value.ToString("O", CultureInfo.InvariantCulture) |> string - let inline sbyte (value: sbyte) = Json.IntegralNumber(uint32 value) - let inline byte (value: byte) = Json.IntegralNumber(uint32 value) - let inline int16 (value: int16) = Json.IntegralNumber(uint32 value) - let inline uint16 (value: uint16) = Json.IntegralNumber(uint32 value) - let inline int (value: int) = Json.IntegralNumber(uint32 value) - let inline uint32 (value: uint32) = Json.IntegralNumber value + let inline sbyte (value: sbyte) = + { new IEncodable with + member _.Encode(helpers) = + helpers.encodeIntegralNumber (uint32 value) + } + + let inline byte (value: byte) = + { new IEncodable with + member _.Encode(helpers) = + helpers.encodeIntegralNumber (uint32 value) + } + + let inline int16 (value: int16) = + { new IEncodable with + member _.Encode(helpers) = + helpers.encodeIntegralNumber (uint32 value) + } + + let inline uint16 (value: uint16) = + { new IEncodable with + member _.Encode(helpers) = + helpers.encodeIntegralNumber (uint32 value) + } + + let inline int (value: int) = + { new IEncodable with + member _.Encode(helpers) = + helpers.encodeIntegralNumber (uint32 value) + } + + let inline uint32 (value: uint32) = + { new IEncodable with + member _.Encode(helpers) = helpers.encodeIntegralNumber (value) + } let inline int64 (value: int64) = value.ToString(CultureInfo.InvariantCulture) |> string @@ -49,9 +129,9 @@ module Encode = let inline uint64 (value: uint64) = value.ToString(CultureInfo.InvariantCulture) |> string - let inline unit () = Json.Unit + let inline unit () = nil - let tuple2 (enc1: Encoder<'T1>) (enc2: Encoder<'T2>) (v1, v2) : Json = + let tuple2 (enc1: Encoder<'T1>) (enc2: Encoder<'T2>) (v1, v2) : IEncodable = array [| enc1 v1 @@ -63,7 +143,7 @@ module Encode = (enc2: Encoder<'T2>) (enc3: Encoder<'T3>) (v1, v2, v3) - : Json + : IEncodable = array [| @@ -78,7 +158,7 @@ module Encode = (enc3: Encoder<'T3>) (enc4: Encoder<'T4>) (v1, v2, v3, v4) - : Json + : IEncodable = array [| @@ -95,7 +175,7 @@ module Encode = (enc4: Encoder<'T4>) (enc5: Encoder<'T5>) (v1, v2, v3, v4, v5) - : Json + : IEncodable = array [| @@ -114,7 +194,7 @@ module Encode = (enc5: Encoder<'T5>) (enc6: Encoder<'T6>) (v1, v2, v3, v4, v5, v6) - : Json + : IEncodable = array [| @@ -135,7 +215,7 @@ module Encode = (enc6: Encoder<'T6>) (enc7: Encoder<'T7>) (v1, v2, v3, v4, v5, v6, v7) - : Json + : IEncodable = array [| @@ -158,7 +238,7 @@ module Encode = (enc7: Encoder<'T7>) (enc8: Encoder<'T8>) (v1, v2, v3, v4, v5, v6, v7, v8) - : Json + : IEncodable = array [| @@ -177,7 +257,7 @@ module Encode = (keyEncoder: Encoder<'key>) (valueEncoder: Encoder<'value>) (values: Map<'key, 'value>) - : Json + : IEncodable = values |> Map.toList @@ -190,44 +270,41 @@ module Encode = module Enum = - let byte<'TEnum when 'TEnum: enum> (value: 'TEnum) : Json = + let byte<'TEnum when 'TEnum: enum> (value: 'TEnum) : IEncodable = LanguagePrimitives.EnumToValue value |> byte - let sbyte<'TEnum when 'TEnum: enum> (value: 'TEnum) : Json = + let sbyte<'TEnum when 'TEnum: enum> + (value: 'TEnum) + : IEncodable + = LanguagePrimitives.EnumToValue value |> sbyte - let int16<'TEnum when 'TEnum: enum> (value: 'TEnum) : Json = + let int16<'TEnum when 'TEnum: enum> + (value: 'TEnum) + : IEncodable + = LanguagePrimitives.EnumToValue value |> int16 - let uint16<'TEnum when 'TEnum: enum> (value: 'TEnum) : Json = + let uint16<'TEnum when 'TEnum: enum> + (value: 'TEnum) + : IEncodable + = LanguagePrimitives.EnumToValue value |> uint16 - let int<'TEnum when 'TEnum: enum> (value: 'TEnum) : Json = + let int<'TEnum when 'TEnum: enum> (value: 'TEnum) : IEncodable = LanguagePrimitives.EnumToValue value |> int - let uint32<'TEnum when 'TEnum: enum> (value: 'TEnum) : Json = + let uint32<'TEnum when 'TEnum: enum> + (value: 'TEnum) + : IEncodable + = LanguagePrimitives.EnumToValue value |> uint32 - let option (encoder: 'a -> Json) = + let option (encoder: Encoder<'a>) = Option.map encoder >> Option.defaultWith (fun _ -> nil) - let rec toJsonValue (helpers: IEncoderHelpers<'JsonValue>) (json: Json) = - match json with - | Json.String value -> helpers.encodeString value - | Json.IntegralNumber value -> helpers.encodeIntegralNumber value - | Json.Object values -> - let o = helpers.createEmptyObject () - - values - |> Seq.iter (fun (k, v) -> - helpers.setPropertyOnObject (o, k, toJsonValue helpers v) - ) - - o - | Json.Char value -> helpers.encodeChar value - | Json.DecimalNumber value -> helpers.encodeDecimalNumber value - | Json.Null -> helpers.encodeNull () - | Json.Boolean value -> helpers.encodeBool value - | Json.Array value -> - value |> Array.map (toJsonValue helpers) |> helpers.encodeArray - | Json.Unit -> helpers.encodeNull () + let inline toJsonValue + (helpers: IEncoderHelpers<'JsonValue>) + (json: IEncodable) + = + json.Encode(helpers) diff --git a/packages/Thoth.Json.Core/Types.fs b/packages/Thoth.Json.Core/Types.fs index d2a23fc..b8eac69 100644 --- a/packages/Thoth.Json.Core/Types.fs +++ b/packages/Thoth.Json.Core/Types.fs @@ -73,4 +73,9 @@ type Json = | IntegralNumber of uint32 | Unit -type Encoder<'T> = 'T -> Json +type IEncodable = + abstract member Encode<'JsonValue> : + helpers: IEncoderHelpers<'JsonValue> -> + 'JsonValue + +type Encoder<'T> = 'T -> IEncodable diff --git a/packages/Thoth.Json.JavaScript/CHANGELOG.md b/packages/Thoth.Json.JavaScript/CHANGELOG.md index d29c3fe..423c08b 100644 --- a/packages/Thoth.Json.JavaScript/CHANGELOG.md +++ b/packages/Thoth.Json.JavaScript/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +* Rework encoder API to not need a custom DU ([GH-188](https://github.com/thoth-org/Thoth.Json/pull/188/)) + ### Added * `Decode.unsafeFromString` diff --git a/packages/Thoth.Json.JavaScript/Encode.fs b/packages/Thoth.Json.JavaScript/Encode.fs index d3152b4..f5cae51 100644 --- a/packages/Thoth.Json.JavaScript/Encode.fs +++ b/packages/Thoth.Json.JavaScript/Encode.fs @@ -27,6 +27,6 @@ module Encode = member _.encodeIntegralNumber value = box value } - let toString (space: int) (value: Json) : string = + let toString (space: int) (value: IEncodable) : string = let json = Encode.toJsonValue helpers value JS.JSON.stringify (json, space = space) diff --git a/packages/Thoth.Json.Newtonsoft/CHANGELOG.md b/packages/Thoth.Json.Newtonsoft/CHANGELOG.md index d29c3fe..423c08b 100644 --- a/packages/Thoth.Json.Newtonsoft/CHANGELOG.md +++ b/packages/Thoth.Json.Newtonsoft/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +* Rework encoder API to not need a custom DU ([GH-188](https://github.com/thoth-org/Thoth.Json/pull/188/)) + ### Added * `Decode.unsafeFromString` diff --git a/packages/Thoth.Json.Newtonsoft/Encode.fs b/packages/Thoth.Json.Newtonsoft/Encode.fs index d7b78a3..7cd6401 100644 --- a/packages/Thoth.Json.Newtonsoft/Encode.fs +++ b/packages/Thoth.Json.Newtonsoft/Encode.fs @@ -37,7 +37,7 @@ module Encode = JValue(uint64 value) } - let toString (space: int) (value: Json) : string = + let toString (space: int) (value: IEncodable) : string = let format = if space = 0 then Formatting.None diff --git a/packages/Thoth.Json.Python/CHANGELOG.md b/packages/Thoth.Json.Python/CHANGELOG.md index f08504d..f4808b5 100644 --- a/packages/Thoth.Json.Python/CHANGELOG.md +++ b/packages/Thoth.Json.Python/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +* Rework encoder API to not need a custom DU ([GH-188](https://github.com/thoth-org/Thoth.Json/pull/188/)) + ## 0.2.0 - 2024-04-03 ### Added diff --git a/packages/Thoth.Json.Python/Encode.fs b/packages/Thoth.Json.Python/Encode.fs index 3db1f8b..93ab493 100644 --- a/packages/Thoth.Json.Python/Encode.fs +++ b/packages/Thoth.Json.Python/Encode.fs @@ -21,13 +21,17 @@ module Encode = o?(key) <- value member _.encodeArray values = values - member _.encodeList values = JS.Constructors.Array.from values - member _.encodeSeq values = JS.Constructors.Array.from values + + member this.encodeList values = + values |> List.toArray |> this.encodeArray + + member this.encodeSeq values = + values |> Seq.toArray |> this.encodeArray member _.encodeIntegralNumber value = box value } - let toString (space: int) (value: Json) : string = + let toString (space: int) (value: IEncodable) : string = let json = Encode.toJsonValue helpers value // If we pass an indention of 0 to Python's json.dumps, it will // insert newlines, between each element instead of compressing diff --git a/tests/Thoth.Json.Tests/Util.fs b/tests/Thoth.Json.Tests/Util.fs index 489fe53..b53d116 100644 --- a/tests/Thoth.Json.Tests/Util.fs +++ b/tests/Thoth.Json.Tests/Util.fs @@ -25,7 +25,7 @@ open Thoth.Json.Core // abstract type IEncode = - abstract toString: int -> Json -> string + abstract toString: int -> IEncodable -> string type IDecode = abstract fromString<'T> : Decoder<'T> -> string -> Result<'T, string>