Skip to content
Open
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
99 changes: 56 additions & 43 deletions FSharp.Json/Core.fs
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@
let enumMode = getEnumMode config jsonField
match enumMode with
| EnumMode.Value ->
match baseT with

Check warning on line 81 in FSharp.Json/Core.fs

View workflow job for this annotation

GitHub Actions / build

Incomplete pattern matches on this expression.

Check warning on line 81 in FSharp.Json/Core.fs

View workflow job for this annotation

GitHub Actions / build

Incomplete pattern matches on this expression.
| t when t = typeof<int> ->
| t when Type.(=)(t, typeof<int>) ->
let enumValue = decimal (value :?> int)
JsonValue.Number enumValue
| t when t = typeof<byte> ->
| t when Type.(=)(t, typeof<byte>) ->
let enumValue = decimal (value :?> byte)
JsonValue.Number enumValue
| t when t = typeof<char> ->
| t when Type.(=)(t, typeof<char>) ->
let enumValue = $"%c{value :?> char}"
JsonValue.String enumValue
| EnumMode.Name ->
Expand All @@ -107,45 +107,45 @@
let t, value = transformToTargetType t value jsonField.Transform
let t = getUntypedType t value
match t with
| t when t = typeof<unit> ->
| t when Type.(=)(t, typeof<unit>) ->
JsonValue.Null
| t when t = typeof<uint16> ->
| t when Type.(=)(t, typeof<uint16>) ->
JsonValue.Number (decimal (value :?> uint16))
| t when t = typeof<int16> ->
| t when Type.(=)(t, typeof<int16>) ->
JsonValue.Number (decimal (value :?> int16))
| t when t = typeof<int> ->
| t when Type.(=)(t, typeof<int>) ->
JsonValue.Number (decimal (value :?> int))
| t when t = typeof<uint32> ->
| t when Type.(=)(t, typeof<uint32>) ->
JsonValue.Number (decimal (value :?> uint32))
| t when t = typeof<int64> ->
| t when Type.(=)(t, typeof<int64>) ->
JsonValue.Number (decimal (value :?> int64))
| t when t = typeof<uint64> ->
| t when Type.(=)(t, typeof<uint64>) ->
JsonValue.Number (decimal (value :?> uint64))
| t when t = typeof<bigint> ->
| t when Type.(=)(t, typeof<bigint>) ->
JsonValue.Number (decimal (value :?> bigint))
| t when t = typeof<single> ->
| t when Type.(=)(t, typeof<single>) ->
JsonValue.Float (float (value :?> single))
| t when t = typeof<float> ->
| t when Type.(=)(t, typeof<float>) ->
JsonValue.Float (value :?> float)
| t when t = typeof<decimal> ->
| t when Type.(=)(t, typeof<decimal>) ->
JsonValue.Number (value :?> decimal)
| t when t = typeof<byte> ->
| t when Type.(=)(t, typeof<byte>) ->
JsonValue.Number (decimal (value :?> byte))
| t when t = typeof<sbyte> ->
| t when Type.(=)(t, typeof<sbyte>) ->
JsonValue.Number (decimal (value :?> sbyte))
| t when t = typeof<bool> ->
| t when Type.(=)(t, typeof<bool>) ->
JsonValue.Boolean (value :?> bool)
| t when t = typeof<string> ->
| t when Type.(=)(t, typeof<string>) ->
JsonValue.String (value :?> string)
| t when t = typeof<char> ->
| t when Type.(=)(t, typeof<char>) ->
JsonValue.String (string(value :?> char))
| t when t = typeof<DateTime> ->
| t when Type.(=)(t, typeof<DateTime>) ->
JsonValue.String ((value :?> DateTime).ToString(jsonField.DateTimeFormat))
| t when t = typeof<DateTimeOffset> ->
| t when Type.(=)(t, typeof<DateTimeOffset>) ->
JsonValue.String ((value :?> DateTimeOffset).ToString(jsonField.DateTimeFormat))
| t when t = typeof<TimeSpan> ->
| t when Type.(=)(t, typeof<TimeSpan>) ->
JsonValue.String ((value :?> TimeSpan).ToString())
| t when t = typeof<Guid> ->
| t when Type.(=)(t, typeof<Guid>) ->
JsonValue.String ((value :?> Guid).ToString())
| t when t.IsEnum ->
serializeEnum t jsonField value
Expand All @@ -169,6 +169,14 @@
match config.serializeNone with
| Null -> Some JsonValue.Null
| Omit -> None
| t when isVOption t ->
let unwrapedValue = unwrapVOption t value
match unwrapedValue with
| ValueSome value -> Some (serializeNonOption (getOptionType t) jsonField value)
| ValueNone ->
match config.serializeNone with
| Null -> Some JsonValue.Null
| Omit -> None
| _ -> Some (serializeNonOption t jsonField value)

let serializeUnwrapOptionWithNull (t: Type) (jsonField: JsonField) (value: obj): JsonValue =
Expand All @@ -178,6 +186,11 @@
match unwrapedValue with
| Some value -> serializeNonOption (getOptionType t) jsonField value
| None -> JsonValue.Null
| t when isVOption t ->
let unwrapedValue = unwrapVOption t value
match unwrapedValue with
| ValueSome value -> serializeNonOption (getOptionType t) jsonField value
| ValueNone -> JsonValue.Null
| _ -> serializeNonOption t jsonField value

let serializeProperty (therec: obj) (prop: PropertyInfo): (string*JsonValue) option =
Expand Down Expand Up @@ -299,7 +312,7 @@
let enumMode = getEnumMode config jsonField
match enumMode with
| EnumMode.Value ->
match baseT with

Check warning on line 315 in FSharp.Json/Core.fs

View workflow job for this annotation

GitHub Actions / build

Incomplete pattern matches on this expression.

Check warning on line 315 in FSharp.Json/Core.fs

View workflow job for this annotation

GitHub Actions / build

Incomplete pattern matches on this expression.
| baseT when baseT = typeof<int> ->
let enumValue = JsonValueHelpers.getInt path jValue
Enum.ToObject(t, enumValue)
Expand Down Expand Up @@ -334,43 +347,43 @@
let t = getUntypedType path t jValue
let jValue =
match t with
| t when t = typeof<int16> ->
| t when Type.(=)(t, typeof<int16>) ->
JsonValueHelpers.getInt16 path jValue :> obj
| t when t = typeof<uint16> ->
| t when Type.(=)(t, typeof<uint16>) ->
JsonValueHelpers.getUInt16 path jValue :> obj
| t when t = typeof<int> ->
| t when Type.(=)(t, typeof<int>) ->
JsonValueHelpers.getInt path jValue :> obj
| t when t = typeof<uint32> ->
| t when Type.(=)(t, typeof<uint32>) ->
JsonValueHelpers.getUInt32 path jValue :> obj
| t when t = typeof<int64> ->
| t when Type.(=)(t, typeof<int64>) ->
JsonValueHelpers.getInt64 path jValue :> obj
| t when t = typeof<uint64> ->
| t when Type.(=)(t, typeof<uint64>) ->
JsonValueHelpers.getUInt64 path jValue :> obj
| t when t = typeof<bigint> ->
| t when Type.(=)(t, typeof<bigint>) ->
JsonValueHelpers.getBigint path jValue :> obj
| t when t = typeof<single> ->
| t when Type.(=)(t, typeof<single>) ->
JsonValueHelpers.getSingle path jValue :> obj
| t when t = typeof<float> ->
| t when Type.(=)(t, typeof<float>) ->
JsonValueHelpers.getFloat path jValue :> obj
| t when t = typeof<decimal> ->
| t when Type.(=)(t, typeof<decimal>) ->
JsonValueHelpers.getDecimal path jValue :> obj
| t when t = typeof<byte> ->
| t when Type.(=)(t, typeof<byte>) ->
JsonValueHelpers.getByte path jValue :> obj
| t when t = typeof<sbyte> ->
| t when Type.(=)(t, typeof<sbyte>) ->
JsonValueHelpers.getSByte path jValue :> obj
| t when t = typeof<bool> ->
| t when Type.(=)(t, typeof<bool>) ->
JsonValueHelpers.getBool path jValue :> obj
| t when t = typeof<string> ->
| t when Type.(=)(t, typeof<string>) ->
JsonValueHelpers.getString path jValue :> obj
| t when t = typeof<char> ->
| t when Type.(=)(t, typeof<char>) ->
JsonValueHelpers.getChar path jValue :> obj
| t when t = typeof<DateTime> ->
| t when Type.(=)(t, typeof<DateTime>) ->
JsonValueHelpers.getDateTime CultureInfo.InvariantCulture path jValue :> obj
| t when t = typeof<DateTimeOffset> ->
| t when Type.(=)(t, typeof<DateTimeOffset>) ->
JsonValueHelpers.getDateTimeOffset CultureInfo.InvariantCulture path jValue :> obj
| t when t = typeof<TimeSpan> ->
| t when Type.(=)(t, typeof<TimeSpan>) ->
JsonValueHelpers.getTimeSpan path jValue :> obj
| t when t = typeof<Guid> ->
| t when Type.(=)(t, typeof<Guid>) ->
JsonValueHelpers.getGuid path jValue :> obj
| t when t.IsEnum ->
deserializeEnum path t jsonField jValue
Expand All @@ -381,7 +394,7 @@

let deserializeUnwrapOption (path: JsonPath) (t: Type) (jsonField: JsonField) (jvalue: JsonValue option): obj =
match t with
| t when isOption t ->
| t when isOption t || isVOption t ->
match jvalue with
| Some jvalue ->
match jvalue with
Expand Down Expand Up @@ -522,7 +535,7 @@
| JsonValue.String caseName ->
FSharpValue.MakeUnion (caseName |> getUnionCaseInfo path t, null)
| JsonValue.Record fields ->
match jsonUnion.Mode with

Check warning on line 538 in FSharp.Json/Core.fs

View workflow job for this annotation

GitHub Actions / build

Enums may take values outside known cases. For example, the value 'enum<UnionMode> (3)' may indicate a case not covered by the pattern(s).

Check warning on line 538 in FSharp.Json/Core.fs

View workflow job for this annotation

GitHub Actions / build

Enums may take values outside known cases. For example, the value 'enum<UnionMode> (3)' may indicate a case not covered by the pattern(s).
| UnionMode.CaseKeyDiscriminatorField ->
let caseKeyFieldName, caseKeyFieldValue = mustFindField path jsonUnion.CaseKeyField fields
let caseNamePath = caseKeyFieldName |> JsonPathItem.Field |> path.createNew
Expand Down
5 changes: 4 additions & 1 deletion FSharp.Json/FSharp.Json.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
<RepositoryUrl>https://github.com/vsapronov/FSharp.Json</RepositoryUrl>
<Version>0.4.1</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Release'">
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
</PropertyGroup>
<ItemGroup>
<Compile Include="TextConversions.fs" />
<Compile Include="JsonValue.fs" />
Expand Down
7 changes: 5 additions & 2 deletions FSharp.Json/InterfaceTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@ with


/// Represents one item in [JsonPath]
[<Struct>]
type JsonPathItem =
/// Field in JSON object.
| Field of string
| Field of field: string
/// Item in JSON array.
| ArrayItem of int
| ArrayItem of itm: int

/// Represents path in JSON structure
type JsonPath = {
Expand Down Expand Up @@ -119,13 +120,15 @@ type JsonDeserializationError(path: JsonPath, message: string) =
member e.Path = path

/// Modes of serialization of option None value
[<Struct>]
type SerializeNone =
/// Serialize None value as null in JSON.
| Null
/// Omit members with None values in JSON.
| Omit

/// Modes of deserialization of option types
[<Struct>]
type DeserializeOption =
/// Allow members with None value to be omitted in JSON.
| AllowOmit
Expand Down
6 changes: 3 additions & 3 deletions FSharp.Json/JsonValue.fs
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,16 @@ type private JsonParser(jsonText:string, cultureInfo, tolerateErrors) =
elif d >= 'A' && d <= 'F' then int32 d - int32 'A' + 10
else failwith "hexdigit"
let unicodeChar (s:string) =
if s.Length <> 4 then failwith "unicodeChar";
if s.Length <> 4 then failwithf "unicodeChar (%s)" s;
char (hexdigit s.[0] * 4096 + hexdigit s.[1] * 256 + hexdigit s.[2] * 16 + hexdigit s.[3])
let ch = unicodeChar (s.Substring(i+2, 4))
buf.Append(ch) |> ignore
i <- i + 4 // the \ and u will also be skipped past further below
| 'U' ->
ensure(i+9 < s.Length)
let unicodeChar (s:string) =
if s.Length <> 8 then failwith "unicodeChar";
if s.[0..1] <> "00" then failwith "unicodeChar";
if s.Length <> 8 then failwithf "unicodeChar (%s)" s;
if s.[0..1] <> "00" then failwithf "unicodeChar (%s)" s;
UnicodeHelper.getUnicodeSurrogatePair <| System.UInt32.Parse(s, NumberStyles.HexNumber)
let lead, trail = unicodeChar (s.Substring(i+2, 8))
buf.Append(lead) |> ignore
Expand Down
8 changes: 4 additions & 4 deletions FSharp.Json/JsonValueHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,17 @@ module internal JsonValueHelpers =
| JsonValue.String value ->
let jValue = TextConversions.AsDateTime cultureInfo value
match jValue with
| Some jValue -> jValue
| None -> raiseWrongType path "DateTime" jValue
| ValueSome jValue -> jValue
| ValueNone -> raiseWrongType path "DateTime" jValue
| _ -> raiseWrongType path "DateTime" jValue

let getDateTimeOffset cultureInfo (path: JsonPath) (jValue: JsonValue) =
match jValue with
| JsonValue.String value ->
let jValue = AsDateTimeOffset cultureInfo value
match jValue with
| Some jValue -> jValue
| None -> raiseWrongType path "DateTimeOffset" jValue
| ValueSome jValue -> jValue
| ValueNone -> raiseWrongType path "DateTimeOffset" jValue
| _ -> raiseWrongType path "DateTimeOffset" jValue

let getTimeSpan (path: JsonPath) (jValue: JsonValue) =
Expand Down
10 changes: 10 additions & 0 deletions FSharp.Json/Reflection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ module internal Reflection =
let isOption_ (t: Type): bool =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>>

let isVOption_ (t: Type): bool =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<voption<_>>

let getOptionType_ (t: Type): Type =
t.GetGenericArguments().[0]

Expand Down Expand Up @@ -62,6 +65,7 @@ module internal Reflection =
let getTupleElements: Type -> Type [] = FSharpType.GetTupleElements |> cacheResult

let isOption: Type -> bool = isOption_ |> cacheResult
let isVOption: Type -> bool = isOption_ |> cacheResult
let getOptionType: Type -> Type = getOptionType_ |> cacheResult

let isArray: Type -> bool = isArray_ |> cacheResult
Expand All @@ -83,6 +87,12 @@ module internal Reflection =
| 1 -> Some fields.[0]
| _ -> None

let unwrapVOption (t: Type) (value: obj): obj voption =
let _, fields = FSharpValue.GetUnionFields(value, t)
match fields.Length with
| 1 -> ValueSome fields.[0]
| _ -> ValueNone

let optionNone (t: Type): obj =
let casesInfos = getUnionCases t
FSharpValue.MakeUnion(casesInfos.[0], Array.empty)
Expand Down
20 changes: 11 additions & 9 deletions FSharp.Json/TextConversions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ module private Helpers =
/// Convert the result of TryParse to option type
let asOption = function true, v -> ValueSome v | _ -> ValueNone

[<return: Struct>]
let (|StringEqualsIgnoreCase|_|) (s1:string) s2 =
if s1.Equals(s2, StringComparison.OrdinalIgnoreCase)
then Some () else None
then ValueSome () else ValueNone

[<return: Struct>]
let (|OneOfIgnoreCase|_|) set str =
if Array.exists (fun s -> StringComparer.OrdinalIgnoreCase.Compare(s, str) = 0) set then Some() else None
if Array.exists (fun s -> StringComparer.OrdinalIgnoreCase.Compare(s, str) = 0) set then ValueSome() else ValueNone

let regexOptions =
#if FX_NO_REGEX_COMPILATION
Expand Down Expand Up @@ -80,9 +82,9 @@ type internal TextConversions private() =

static member AsBoolean (text:string) =
match text.Trim() with
| StringEqualsIgnoreCase "true" | StringEqualsIgnoreCase "yes" | StringEqualsIgnoreCase "1" -> Some true
| StringEqualsIgnoreCase "false" | StringEqualsIgnoreCase "no" | StringEqualsIgnoreCase "0" -> Some false
| _ -> None
| StringEqualsIgnoreCase "true" | StringEqualsIgnoreCase "yes" | StringEqualsIgnoreCase "1" -> ValueSome true
| StringEqualsIgnoreCase "false" | StringEqualsIgnoreCase "no" | StringEqualsIgnoreCase "0" -> ValueSome false
| _ -> ValueNone

/// Parse date time using either the JSON milliseconds format or using ISO 8601
/// that is, either `/Date(<msec-since-1/1/1970>)/` or something
Expand All @@ -94,17 +96,17 @@ type internal TextConversions private() =
matchesMS.Groups.[1].Value
|> Double.Parse
|> DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds
|> Some
|> ValueSome
else
// Parse ISO 8601 format, fixing time zone if needed
let dateTimeStyles = DateTimeStyles.AllowWhiteSpaces ||| DateTimeStyles.RoundtripKind
match DateTime.TryParse(text, cultureInfo, dateTimeStyles) with
| true, d ->
if d.Kind = DateTimeKind.Unspecified then
new DateTime(d.Ticks, DateTimeKind.Local) |> Some
new DateTime(d.Ticks, DateTimeKind.Local) |> ValueSome
else
Some d
| _ -> None
ValueSome d
| _ -> ValueNone

static member AsTimeSpan (text: string) =
TimeSpan.TryParse(text) |> asOption
Expand Down
4 changes: 2 additions & 2 deletions FSharp.Json/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ module internal Conversions =
// Parse ISO 8601 format, fixing time zone if needed
let dateTimeStyles = DateTimeStyles.AllowWhiteSpaces ||| DateTimeStyles.RoundtripKind ||| DateTimeStyles.AssumeUniversal
match DateTimeOffset.TryParse(text, cultureInfo, dateTimeStyles) with
| true, d -> Some d
| _ -> None
| true, d -> ValueSome d
| _ -> ValueNone
Loading