Skip to content

Commit

Permalink
feat: rework encoder API to not need a custom DU (#188)
Browse files Browse the repository at this point in the history
* Change Encoder type to return IEncodable, deferring concrete representation

* Change rec to inline

* style: remove some parentheses and alias `this` to `_` if not needed

[core]

* fix: fix `encodeList` and `encodeSeq`

[python]

* chore: update changelog files

---------

Co-authored-by: Maxime Mangel <mangel.maxime@protonmail.com>
  • Loading branch information
njlr and MangelMaxime authored May 4, 2024
1 parent e5a1cc7 commit c883b29
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 61 deletions.
4 changes: 4 additions & 0 deletions packages/Thoth.Json.Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
185 changes: 131 additions & 54 deletions packages/Thoth.Json.Core/Encode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,76 @@ open System
[<RequireQualifiedAccess>]
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<string, Json>) : 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<string * IEncodable>) =
{ 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<string, IEncodable>) : IEncodable =
values |> Map.toSeq |> object

let inline bigint (value: bigint) = value.ToString() |> string

Expand All @@ -36,22 +88,50 @@ 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

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
Expand All @@ -63,7 +143,7 @@ module Encode =
(enc2: Encoder<'T2>)
(enc3: Encoder<'T3>)
(v1, v2, v3)
: Json
: IEncodable
=
array
[|
Expand All @@ -78,7 +158,7 @@ module Encode =
(enc3: Encoder<'T3>)
(enc4: Encoder<'T4>)
(v1, v2, v3, v4)
: Json
: IEncodable
=
array
[|
Expand All @@ -95,7 +175,7 @@ module Encode =
(enc4: Encoder<'T4>)
(enc5: Encoder<'T5>)
(v1, v2, v3, v4, v5)
: Json
: IEncodable
=
array
[|
Expand All @@ -114,7 +194,7 @@ module Encode =
(enc5: Encoder<'T5>)
(enc6: Encoder<'T6>)
(v1, v2, v3, v4, v5, v6)
: Json
: IEncodable
=
array
[|
Expand All @@ -135,7 +215,7 @@ module Encode =
(enc6: Encoder<'T6>)
(enc7: Encoder<'T7>)
(v1, v2, v3, v4, v5, v6, v7)
: Json
: IEncodable
=
array
[|
Expand All @@ -158,7 +238,7 @@ module Encode =
(enc7: Encoder<'T7>)
(enc8: Encoder<'T8>)
(v1, v2, v3, v4, v5, v6, v7, v8)
: Json
: IEncodable
=
array
[|
Expand All @@ -177,7 +257,7 @@ module Encode =
(keyEncoder: Encoder<'key>)
(valueEncoder: Encoder<'value>)
(values: Map<'key, 'value>)
: Json
: IEncodable
=
values
|> Map.toList
Expand All @@ -190,44 +270,41 @@ module Encode =

module Enum =

let byte<'TEnum when 'TEnum: enum<byte>> (value: 'TEnum) : Json =
let byte<'TEnum when 'TEnum: enum<byte>> (value: 'TEnum) : IEncodable =
LanguagePrimitives.EnumToValue value |> byte

let sbyte<'TEnum when 'TEnum: enum<sbyte>> (value: 'TEnum) : Json =
let sbyte<'TEnum when 'TEnum: enum<sbyte>>
(value: 'TEnum)
: IEncodable
=
LanguagePrimitives.EnumToValue value |> sbyte

let int16<'TEnum when 'TEnum: enum<int16>> (value: 'TEnum) : Json =
let int16<'TEnum when 'TEnum: enum<int16>>
(value: 'TEnum)
: IEncodable
=
LanguagePrimitives.EnumToValue value |> int16

let uint16<'TEnum when 'TEnum: enum<uint16>> (value: 'TEnum) : Json =
let uint16<'TEnum when 'TEnum: enum<uint16>>
(value: 'TEnum)
: IEncodable
=
LanguagePrimitives.EnumToValue value |> uint16

let int<'TEnum when 'TEnum: enum<int>> (value: 'TEnum) : Json =
let int<'TEnum when 'TEnum: enum<int>> (value: 'TEnum) : IEncodable =
LanguagePrimitives.EnumToValue value |> int

let uint32<'TEnum when 'TEnum: enum<uint32>> (value: 'TEnum) : Json =
let uint32<'TEnum when 'TEnum: enum<uint32>>
(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)
7 changes: 6 additions & 1 deletion packages/Thoth.Json.Core/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions packages/Thoth.Json.JavaScript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion packages/Thoth.Json.JavaScript/Encode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 4 additions & 0 deletions packages/Thoth.Json.Newtonsoft/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion packages/Thoth.Json.Newtonsoft/Encode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions packages/Thoth.Json.Python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions packages/Thoth.Json.Python/Encode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/Thoth.Json.Tests/Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit c883b29

Please sign in to comment.