Skip to content

Commit 70c56e2

Browse files
authored
Merge pull request #4 from vsapronov/version-0.3
Version 0.3
2 parents ac278a2 + 1f8d583 commit 70c56e2

File tree

19 files changed

+337
-80
lines changed

19 files changed

+337
-80
lines changed

FSharp.Json.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{8E6D
3838
docsrc\content\supported_types.md = docsrc\content\supported_types.md
3939
docsrc\content\transform.fsx = docsrc\content\transform.fsx
4040
docsrc\content\unions.fsx = docsrc\content\unions.fsx
41+
docsrc\content\untyped_data.fsx = docsrc\content\untyped_data.fsx
4142
EndProjectSection
4243
EndProject
4344
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ED8079DD-2B06-4030-9F0F-DC548F98E1C4}"

RELEASE_NOTES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
#### 0.3
2+
* Fix for tuples containing option types
3+
* Support for char type
4+
* Support for enums based on byte and char types
5+
* Configurable enum mode
6+
* Configurable unformatted setting
7+
18
#### 0.2
29
* Single case union as wrapped type
310

docsrc/content/enums.fsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,38 @@ let json = Json.serialize data
6161

6262
let deserialized = Json.deserialize<TheNumberEnum> json
6363
// data is { TheNumberEnum.value = NumberEnum.Three }
64+
65+
(**
66+
Default enum behaviour
67+
----------------------
68+
69+
Sometimes it's needed always serialize enum value as it's value.
70+
Annotating each member of any enum type would be cumbersome.
71+
[JsonConfig]() allows to override default enum behaviour.
72+
73+
Check the example below:
74+
*)
75+
#r "FSharp.Json.dll"
76+
open FSharp.Json
77+
78+
type NumberEnum =
79+
| One = 1
80+
| Two = 2
81+
| Three = 3
82+
83+
// value will be represented as enum value name in JSON
84+
type TheNumberEnum = {
85+
value: NumberEnum
86+
}
87+
88+
let data = { TheNumberEnum.value = NumberEnum.Three }
89+
90+
// create configuration instructing to serialize enum as enum value
91+
let config = JsonConfig.create(enumValue = EnumMode.Value)
92+
93+
let json = Json.serializeEx config data
94+
// json is """{"value":3}"""
95+
// value was serialized as enum value which is 3
96+
97+
let deserialized = Json.deserializeEx<TheNumberEnum> config json
98+
// data is { TheNumberEnum.value = NumberEnum.Three }

docsrc/content/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Following pages are describing main features and aspects of FSharp.Json:
5858
* [Enums](enums.html)
5959
* [Unions](unions.html)
6060
* [Type Transform](transform.html)
61+
* [Untyped Data](untyped_data.html)
6162

6263
Each feature of FSharp.Json is thoroughly covered by [unit tests][unit_tests].
6364

docsrc/content/supported_types.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ string
2323
------
2424
Represented as `string` in JSON.
2525

26+
char
27+
----
28+
Represented as `string` with single character in JSON.
29+
2630
bool
2731
----
2832
Represented as `bool` in JSON.
@@ -77,3 +81,7 @@ union
7781
-----
7882
Unions are represented as `object` with special structure in JSON.
7983
Read more on [Unions](unions.html) page.
84+
85+
obj
86+
---
87+
Read [Untyped Data](untyped_data.html) page.

docsrc/content/untyped_data.fsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
(*** hide ***)
2+
#I "../../bin/FSharp.Json"
3+
4+
(**
5+
Untyped Data
6+
============
7+
8+
Using obj type in F# code is bad code smell.
9+
Though FSharp.Json can serialize and deserialize structures without type information.
10+
For allowing obj type in serialization/deserialization allowUntyped flag should be set to `true` on [JsonConfig](reference/fsharp-json-jsonconfig.html).
11+
12+
Serialization of obj
13+
--------------------
14+
15+
When serializing obj FSharp.Json uses real run time type.
16+
17+
Check this example:
18+
*)
19+
20+
#r "FSharp.Json.dll"
21+
open FSharp.Json
22+
23+
// Record type with obj member
24+
type ObjectRecord = {
25+
value: obj
26+
}
27+
28+
// Set string value to obj member
29+
let data = { ObjectRecord.value = "The string" }
30+
31+
// Need to allow untyped data
32+
let config = JsonConfig.create(allowUntyped = true)
33+
34+
let json = Json.serializeEx config data
35+
// json is """{"value": "The string"}"""
36+
// value was serialized as string because it was assigned to string
37+
38+
(**
39+
Deserialization of obj
40+
----------------------
41+
42+
When deserializing obj FSharp.Json assumes the type from JSON.
43+
44+
See example below:
45+
*)
46+
47+
#r "FSharp.Json.dll"
48+
open FSharp.Json
49+
50+
// Record type with obj member
51+
type ObjectRecord = {
52+
value: obj
53+
}
54+
55+
// value is assigned to string
56+
let json = """{"value": "The string"}"""
57+
58+
// Need to allow untyped data
59+
let config = JsonConfig.create(allowUntyped = true)
60+
61+
let data = Json.deserializeEx<ObjectRecord> config json
62+
// data is { ObjectRecord.value = "The string" }
63+
// value was deserialized as string because it was string in JSON

docsrc/tools/templates/template.cshtml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<li><a href="@Root/enums.html">Enums</a></li>
4848
<li><a href="@Root/unions.html">Unions</a></li>
4949
<li><a href="@Root/transform.html">Type Transform</a></li>
50+
<li><a href="@Root/untyped_data.html">Untyped Data</a></li>
5051
<li class="divider"></li>
5152
<li><a href="@Root/reference/index.html">API Reference</a></li>
5253
<li class="divider"></li>

src/FSharp.Json/AssemblyInfo.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ open System.Reflection
55
[<assembly: AssemblyTitleAttribute("FSharp.Json")>]
66
[<assembly: AssemblyProductAttribute("FSharp.Json")>]
77
[<assembly: AssemblyDescriptionAttribute("F# JSON Reflection based serialization library")>]
8-
[<assembly: AssemblyVersionAttribute("0.2")>]
9-
[<assembly: AssemblyFileVersionAttribute("0.2")>]
8+
[<assembly: AssemblyVersionAttribute("0.3")>]
9+
[<assembly: AssemblyFileVersionAttribute("0.3")>]
1010
[<assembly: AssemblyConfigurationAttribute("Release")>]
1111
do ()
1212

1313
module internal AssemblyVersionInformation =
1414
let [<Literal>] AssemblyTitle = "FSharp.Json"
1515
let [<Literal>] AssemblyProduct = "FSharp.Json"
1616
let [<Literal>] AssemblyDescription = "F# JSON Reflection based serialization library"
17-
let [<Literal>] AssemblyVersion = "0.2"
18-
let [<Literal>] AssemblyFileVersion = "0.2"
17+
let [<Literal>] AssemblyVersion = "0.3"
18+
let [<Literal>] AssemblyFileVersion = "0.3"
1919
let [<Literal>] AssemblyConfiguration = "Release"

src/FSharp.Json/Core.fs

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,28 +62,51 @@ module internal Core =
6262
let valueType = value.GetType()
6363
(valueType, value)
6464

65+
let getEnumMode (config: JsonConfig) (jsonField: JsonField) =
66+
match jsonField.EnumValue with
67+
| EnumMode.Default ->
68+
match config.enumValue with
69+
| EnumMode.Default -> EnumMode.Name
70+
| m -> m
71+
| m -> m
72+
6573
let failSerialization (message: string) =
6674
raise (new JsonSerializationError(message))
6775

6876
let rec serialize (config: JsonConfig) (t: Type) (value: obj): JsonValue =
6977
let serializeEnum (t: Type) (jsonField: JsonField) (value: obj): JsonValue =
70-
match jsonField.EnumValue with
78+
let baseT = Enum.GetUnderlyingType t
79+
let enumMode = getEnumMode config jsonField
80+
match enumMode with
7181
| EnumMode.Value ->
72-
let index = decimal (value :?> int)
73-
JsonValue.Number index
82+
match baseT with
83+
| t when t = typeof<int> ->
84+
let enumValue = decimal (value :?> int)
85+
JsonValue.Number enumValue
86+
| t when t = typeof<byte> ->
87+
let enumValue = decimal (value :?> byte)
88+
JsonValue.Number enumValue
89+
| t when t = typeof<char> ->
90+
let enumValue = sprintf "%c" (value :?> char)
91+
JsonValue.String enumValue
7492
| EnumMode.Name ->
7593
let strvalue = Enum.GetName(t, value)
7694
JsonValue.String strvalue
7795
| mode -> failSerialization <| sprintf "Failed to serialize enum %s, unsupported enum mode: %A" t.Name mode
7896

97+
let getUntypedType (t: Type) (value: obj): Type =
98+
if t = typeof<obj> then
99+
if config.allowUntyped then
100+
value.GetType()
101+
else
102+
failSerialization <| "Failed to serialize untyped data, allowUntyped set to false"
103+
else t
104+
79105
let serializeNonOption (t: Type) (jsonField: JsonField) (value: obj): JsonValue =
80106
match jsonField.AsJson with
81107
| false ->
82108
let t, value = transformToTargetType t value jsonField.Transform
83-
let t =
84-
if t = typeof<obj> then
85-
value.GetType()
86-
else t
109+
let t = getUntypedType t value
87110
match t with
88111
| t when t = typeof<int> ->
89112
JsonValue.Number (decimal (value :?> int))
@@ -97,6 +120,8 @@ module internal Core =
97120
JsonValue.Boolean (value :?> bool)
98121
| t when t = typeof<string> ->
99122
JsonValue.String (value :?> string)
123+
| t when t = typeof<char> ->
124+
JsonValue.String (string(value :?> char))
100125
| t when t = typeof<DateTime> ->
101126
JsonValue.String ((value :?> DateTime).ToString(jsonField.DateTimeFormat))
102127
| t when t = typeof<DateTimeOffset> ->
@@ -127,6 +152,15 @@ module internal Core =
127152
| Omit -> None
128153
| _ -> Some (serializeNonOption t jsonField value)
129154

155+
let serializeUnwrapOptionWithNull (t: Type) (jsonField: JsonField) (value: obj): JsonValue =
156+
match t with
157+
| t when isOption t ->
158+
let unwrapedValue = unwrapOption t value
159+
match unwrapedValue with
160+
| Some value -> serializeNonOption (getOptionType t) jsonField value
161+
| None -> JsonValue.Null
162+
| _ -> serializeNonOption t jsonField value
163+
130164
let serializeProperty (therec: obj) (prop: PropertyInfo): (string*JsonValue) option =
131165
let jsonField = getJsonFieldProperty prop
132166
let propValue = prop.GetValue(therec, Array.empty)
@@ -139,7 +173,17 @@ module internal Core =
139173
let serializeEnumerable (values: IEnumerable): JsonValue =
140174
let items =
141175
values.Cast<Object>()
142-
|> Seq.map (fun value -> serializeUnwrapOption (value.GetType()) JsonField.Default value)
176+
|> Seq.map (fun value ->
177+
serializeUnwrapOption (value.GetType()) JsonField.Default value)
178+
|> Seq.map (someOrDefault JsonValue.Null)
179+
items |> Array.ofSeq |> JsonValue.Array
180+
181+
let serializeTupleItems (types: Type seq) (values: IEnumerable): JsonValue =
182+
let items =
183+
values.Cast<Object>()
184+
|> Seq.zip types
185+
|> Seq.map (fun (t, value) ->
186+
serializeUnwrapOption t JsonField.Default value)
143187
|> Seq.map (someOrDefault JsonValue.Null)
144188
items |> Array.ofSeq |> JsonValue.Array
145189

@@ -162,13 +206,15 @@ module internal Core =
162206
let serializeUnion (t: Type) (theunion: obj): JsonValue =
163207
let caseInfo, values = FSharpValue.GetUnionFields(theunion, t)
164208
let jsonField = getJsonFieldUnionCase caseInfo
209+
let types = caseInfo.GetFields() |> Array.map (fun p -> p.PropertyType)
165210
let jvalue =
166211
match values.Length with
167212
| 1 ->
168213
let caseValue = values.[0]
169-
serializeNonOption (caseValue.GetType()) jsonField caseValue
214+
let caseType = types.[0]
215+
serializeUnwrapOptionWithNull caseType jsonField caseValue
170216
| _ ->
171-
serializeEnumerable values
217+
serializeTupleItems types values
172218
let unionCases = getUnionCases caseInfo.DeclaringType
173219
match unionCases.Length with
174220
| 1 -> jvalue
@@ -189,7 +235,7 @@ module internal Core =
189235
| t when isMap t -> serializeKvpEnumerable (value :?> IEnumerable)
190236
| t when isArray t -> serializeEnumerable (value :?> IEnumerable)
191237
| t when isList t -> serializeEnumerable (value :?> IEnumerable)
192-
| t when isTuple t -> serializeEnumerable (FSharpValue.GetTupleFields value)
238+
| t when isTuple t -> serializeTupleItems (getTupleElements t) (FSharpValue.GetTupleFields value)
193239
| t when isUnion t -> serializeUnion t value
194240
| t ->
195241
let msg = sprintf "Failed to serialize, must be one of following types: record, map, array, list, tuple, union. Type is: %s." t.Name
@@ -221,15 +267,34 @@ module internal Core =
221267

222268
let rec deserialize (config: JsonConfig) (path: JsonPath) (t: Type) (jvalue: JsonValue): obj =
223269
let deserializeEnum (path: JsonPath) (t: Type) (jsonField: JsonField) (jvalue: JsonValue): obj =
224-
match jsonField.EnumValue with
270+
let baseT = Enum.GetUnderlyingType t
271+
let enumMode = getEnumMode config jsonField
272+
match enumMode with
225273
| EnumMode.Value ->
226-
let index = JsonValueHelpers.getInt path jvalue
227-
Enum.ToObject(t, index)
274+
match baseT with
275+
| baseT when baseT = typeof<int> ->
276+
let enumValue = JsonValueHelpers.getInt path jvalue
277+
Enum.ToObject(t, enumValue)
278+
| baseT when baseT = typeof<byte> ->
279+
let enumValue = JsonValueHelpers.getByte path jvalue
280+
Enum.ToObject(t, enumValue)
281+
| baseT when baseT = typeof<char> ->
282+
let enumValue = JsonValueHelpers.getChar path jvalue
283+
Enum.ToObject(t, enumValue)
228284
| EnumMode.Name ->
229285
let valueStr = JsonValueHelpers.getString path jvalue
230286
Enum.Parse(t, valueStr)
231287
| mode -> failDeserialization path <| sprintf "Failed to deserialize enum %s, unsupported enum mode: %A" t.Name mode
232288

289+
let getUntypedType (path: JsonPath) (t: Type) (jvalue: JsonValue): Type =
290+
match t with
291+
| t when t = typeof<obj> ->
292+
if config.allowUntyped then
293+
getJsonValueType jvalue
294+
else
295+
failDeserialization path <| sprintf "Failed to deserialize object, allowUntyped set to false"
296+
| t -> t
297+
233298
let deserializeNonOption (path: JsonPath) (t: Type) (jsonField: JsonField) (jvalue: JsonValue): obj =
234299
match jsonField.AsJson with
235300
| true ->
@@ -238,10 +303,7 @@ module internal Core =
238303
| _ -> jvalue.ToString(JsonSaveOptions.DisableFormatting) :> obj
239304
| false ->
240305
let t = getTargetType t jsonField
241-
let t =
242-
match t with
243-
| t when t = typeof<obj> -> getJsonValueType jvalue
244-
| t -> t
306+
let t = getUntypedType path t jvalue
245307
let jvalue =
246308
match t with
247309
| t when t = typeof<int> ->
@@ -256,6 +318,8 @@ module internal Core =
256318
JsonValueHelpers.getBool path jvalue :> obj
257319
| t when t = typeof<string> ->
258320
JsonValueHelpers.getString path jvalue :> obj
321+
| t when t = typeof<char> ->
322+
JsonValueHelpers.getChar path jvalue :> obj
259323
| t when t = typeof<DateTime> ->
260324
JsonValueHelpers.getDateTime CultureInfo.InvariantCulture path jvalue :> obj
261325
| t when t = typeof<DateTimeOffset> ->

src/FSharp.Json/Interface.fs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ module Json =
2828
/// Serailizes F# object into JSON. Uses provided [JsonConfig].
2929
let serializeEx (config: JsonConfig) (theobj: obj): string =
3030
let json = Core.serialize config (theobj.GetType()) theobj
31-
json.ToString()
31+
let saveOptions =
32+
match config.unformatted with
33+
| true -> JsonSaveOptions.DisableFormatting
34+
| false -> JsonSaveOptions.None
35+
json.ToString(saveOptions)
3236

33-
/// Serailizes F# object into unformatted JSON. Uses provided [JsonConfig].
34-
let serializeExU (config: JsonConfig) (theobj: obj): string =
35-
let json = Core.serialize config (theobj.GetType()) theobj
36-
json.ToString(JsonSaveOptions.DisableFormatting)
37-
3837
/// Deserailizes JSON into F# type provided as generic parameter. Uses provided [JsonConfig].
3938
let deserializeEx<'T> (config: JsonConfig) (json: string): 'T =
4039
let value = JsonValue.Parse(json)
@@ -44,7 +43,9 @@ module Json =
4443
let serialize (theobj: obj) = serializeEx JsonConfig.Default theobj
4544

4645
/// Serailizes F# object into unformatted JSON. Uses default [JsonConfig].
47-
let serializeU (theobj: obj) = serializeExU JsonConfig.Default theobj
46+
let serializeU (theobj: obj) =
47+
let config = JsonConfig.create(unformatted = true)
48+
serializeEx config theobj
4849

4950
/// Deserailizes JSON into F# type provided as generic parameter. Uses default [JsonConfig].
5051
let deserialize<'T> (json: string) = deserializeEx<'T> JsonConfig.Default json

0 commit comments

Comments
 (0)