Help required: Move field
davedawkins opened this issue · 7 comments
I have this JSON on disk
{
"foo": [ "cat", "dog" ]
}
which maps to type
type Thing = { foo : string[] }
I have changed Thing to be
type Thing = { foo : string[]; bar : string [] }
and now the foo
on disk must go into Thing.bar
(and Thing.foo
will be empty)
I know how to handle bar
being missing, by using custom decoders, but cannot figure out how to write
a Thing
decoder which can read foo
into Thing.bar
if bar
is missing in the JSON.
Thank you
Perhaps something like this?
#r "nuget: Thoth.Json.Net, 11.0"
type Thing = { foo : string[]; bar : string [] }
module Thing =
open Thoth.Json.Net
let decode : Decoder<Thing> =
Decode.object
(fun get ->
{
foo = get.Required.Field "foo" (Decode.array Decode.string)
bar =
get.Optional.Field "bar" (Decode.array Decode.string)
|> Option.defaultValue [||]
})
Hello @davedawkins,
I am not sure to understand everything. Could you please provide the expected values associated to the JSON?
For example:
type Thing = { foo : string[]; bar : string [] }
let json =
"""
{
"foo": [ "cat", "dog" ]
}
"""
let expected =
{
foo = [ "cat", "dog" ]
bar = []
}
If there are different scenario I would be interested in having them too.
Hello @davedawkins,
I am not sure to understand everything. Could you please provide the expected values associated to the JSON?
...
If there are different scenario I would be interested in having them too.
type Thing = { foo : string[]; bar : string [] } // bar is a new field for Thing v2
let json1 = // Legacy Thing v1 data
"""
{
"foo": [ "cat", "dog" ] // Must now target Thing.bar
}
"""
let json2 = // Example Thing v2 data
"""
{
"foo": [ "apple", "peach" ]
"bar": [ "cat", "dog" ]
}
"""
let expectedFromJson1 =
{
foo = []
bar = [ "cat", "dog" ]
}
let expectedFromJson2 =
{
foo = [ "apple", "peach" ]
bar = [ "cat", "dog" ]
}
The rule is:
- if JSON "bar" is missing, this is Thing.v1 data, and so we are reading JSON "foo" into Thing.bar, and giving Thing.foo a default value
- if JSON "bar" is present, use standard mappings (JSON "foo" -> Thing.foo, JSON "bar" -> Thing.bar)
@njlr Thank you, but it misses the JSON "foo" to Thing.bar mapping
Please don't judge me for creating this upgrade scenario....
Thank you!
My best effort
type Thing_v1 = { foo : string[] }
let decoderThing : Decoder<Thing> =
fun path value ->
let r =
if (JsHelpers.jsPropertyExists("bar",value)) then
Decode.Auto.fromString<Thing>( Encode.Auto.toString(value))
else
match Decode.Auto.fromString<Thing_v1>( Encode.Auto.toString(value)) with
| Ok tv1 -> Ok ({ foo = [||]; bar = tv1.foo })
| Error _ as e -> e
match r with
| Ok v -> Ok v
| Error msg -> Error <| DecoderError (msg, ErrorReason.FailMessage msg)
I think you want Decode.oneOf
let decoderThing : Decoder<Thing> =
Decode.oneOf
[
decoderV2
decoderV1
]
Beware that the order matters here!
It will attempt decoderV2
first and only try decoderV1
if that fails.
You can also use Decode.map
if you need to do a transformation:
let decoderThing : Decoder<Thing> =
Decode.oneOf
[
decoderV2
decoderV1 |> Decode.map convertV1ToV2
]
Beautiful thank you
Can confirm this works, thank you so much
let decoder : Decoder<EntityDTO> =
Decode.oneOf [
Decode.Auto.generateDecoder<EntityDTO>()
Decode.Auto.generateDecoder<EntityDTO_v1>() |> Decode.map mapE1E2
]