Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experiment/issue-175-encoders-without-json-du #188

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading