diff --git a/FSharp.Json/Core.fs b/FSharp.Json/Core.fs index 747fc17..fb83cf5 100644 --- a/FSharp.Json/Core.fs +++ b/FSharp.Json/Core.fs @@ -79,13 +79,13 @@ module internal Core = match enumMode with | EnumMode.Value -> match baseT with - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> let enumValue = decimal (value :?> int) JsonValue.Number enumValue - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> let enumValue = decimal (value :?> byte) JsonValue.Number enumValue - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> let enumValue = $"%c{value :?> char}" JsonValue.String enumValue | EnumMode.Name -> @@ -107,45 +107,45 @@ module internal Core = let t, value = transformToTargetType t value jsonField.Transform let t = getUntypedType t value match t with - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Null - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> uint16)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> int16)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> int)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> uint32)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> int64)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> uint64)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> bigint)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Float (float (value :?> single)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Float (value :?> float) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (value :?> decimal) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> byte)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Number (decimal (value :?> sbyte)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.Boolean (value :?> bool) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.String (value :?> string) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.String (string(value :?> char)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.String ((value :?> DateTime).ToString(jsonField.DateTimeFormat)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.String ((value :?> DateTimeOffset).ToString(jsonField.DateTimeFormat)) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.String ((value :?> TimeSpan).ToString()) - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValue.String ((value :?> Guid).ToString()) | t when t.IsEnum -> serializeEnum t jsonField value @@ -169,6 +169,14 @@ module internal Core = 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 = @@ -178,6 +186,11 @@ module internal Core = 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 = @@ -334,43 +347,43 @@ module internal Core = let t = getUntypedType path t jValue let jValue = match t with - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getInt16 path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getUInt16 path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getInt path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getUInt32 path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getInt64 path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getUInt64 path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getBigint path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getSingle path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getFloat path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getDecimal path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getByte path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getSByte path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getBool path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getString path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getChar path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getDateTime CultureInfo.InvariantCulture path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getDateTimeOffset CultureInfo.InvariantCulture path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getTimeSpan path jValue :> obj - | t when t = typeof -> + | t when Type.(=)(t, typeof) -> JsonValueHelpers.getGuid path jValue :> obj | t when t.IsEnum -> deserializeEnum path t jsonField jValue @@ -381,7 +394,7 @@ module internal Core = 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 diff --git a/FSharp.Json/FSharp.Json.fsproj b/FSharp.Json/FSharp.Json.fsproj index 41c49b0..11cb35c 100644 --- a/FSharp.Json/FSharp.Json.fsproj +++ b/FSharp.Json/FSharp.Json.fsproj @@ -10,7 +10,10 @@ https://github.com/vsapronov/FSharp.Json 0.4.1 - + + true + true + diff --git a/FSharp.Json/InterfaceTypes.fs b/FSharp.Json/InterfaceTypes.fs index c8ce7a5..c50d79d 100644 --- a/FSharp.Json/InterfaceTypes.fs +++ b/FSharp.Json/InterfaceTypes.fs @@ -77,11 +77,12 @@ with /// Represents one item in [JsonPath] +[] 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 = { @@ -119,6 +120,7 @@ type JsonDeserializationError(path: JsonPath, message: string) = member e.Path = path /// Modes of serialization of option None value +[] type SerializeNone = /// Serialize None value as null in JSON. | Null @@ -126,6 +128,7 @@ type SerializeNone = | Omit /// Modes of deserialization of option types +[] type DeserializeOption = /// Allow members with None value to be omitted in JSON. | AllowOmit diff --git a/FSharp.Json/JsonValue.fs b/FSharp.Json/JsonValue.fs index 7a65611..a1a41c1 100644 --- a/FSharp.Json/JsonValue.fs +++ b/FSharp.Json/JsonValue.fs @@ -212,7 +212,7 @@ 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 @@ -220,8 +220,8 @@ type private JsonParser(jsonText:string, cultureInfo, tolerateErrors) = | '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 diff --git a/FSharp.Json/JsonValueHelpers.fs b/FSharp.Json/JsonValueHelpers.fs index 0fd0ac2..fbbf092 100644 --- a/FSharp.Json/JsonValueHelpers.fs +++ b/FSharp.Json/JsonValueHelpers.fs @@ -102,8 +102,8 @@ 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) = @@ -111,8 +111,8 @@ module internal JsonValueHelpers = | 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) = diff --git a/FSharp.Json/Reflection.fs b/FSharp.Json/Reflection.fs index 9a6f789..810c5b9 100644 --- a/FSharp.Json/Reflection.fs +++ b/FSharp.Json/Reflection.fs @@ -9,6 +9,9 @@ module internal Reflection = let isOption_ (t: Type): bool = t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> + let isVOption_ (t: Type): bool = + t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> + let getOptionType_ (t: Type): Type = t.GetGenericArguments().[0] @@ -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 @@ -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) diff --git a/FSharp.Json/TextConversions.fs b/FSharp.Json/TextConversions.fs index 75007f2..eee1454 100644 --- a/FSharp.Json/TextConversions.fs +++ b/FSharp.Json/TextConversions.fs @@ -16,12 +16,14 @@ module private Helpers = /// Convert the result of TryParse to option type let asOption = function true, v -> ValueSome v | _ -> ValueNone + [] let (|StringEqualsIgnoreCase|_|) (s1:string) s2 = if s1.Equals(s2, StringComparison.OrdinalIgnoreCase) - then Some () else None + then ValueSome () else ValueNone + [] 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 @@ -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()/` or something @@ -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 diff --git a/FSharp.Json/Utils.fs b/FSharp.Json/Utils.fs index 4b3a877..5d6cc0e 100644 --- a/FSharp.Json/Utils.fs +++ b/FSharp.Json/Utils.fs @@ -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