F # совпадает с результатами запроса. Есть ли элегантный способ сделать это? - PullRequest
0 голосов
/ 06 ноября 2019

У меня есть результат типа JObject от анализа json:

let j = JObject.Parse x

код, который мне нужно сделать, выглядит так:

if j = null then
    ... do stuff
else if j["aa"] <> null then
    ... do stuff
else if j["bb"] <> null then
    ... do stuff
else if j["cc"] <> null and j["dd"] <> null then
    ... do stuff

есть ли чистый способ сделатьэто совпадение?

делать заявления типа

| _ when j.["error"] <> null ->

не кажется супер чистым. Можно ли сделать это лучше?

Ответы [ 4 ]

2 голосов
/ 06 ноября 2019

Если вы создаете активный шаблон, который возвращает совпадающие JToken ...

let (|NonNull|_|) prop (o : JObject) =
    o.[prop] |> Option.ofObj

, вы могли бы написать что-то вроде:

let handleAA (a : JToken) = ()

match JObject.Parse "{}" with
| null -> () // ...
| NonNull "aa" a -> handleAA a
| NonNull "bb" b & NonNull "cc" c -> ()
| _ -> () // all other

Обновление

Если вам нужно больше энергии, Active Patterns в изобилии ...

let (|J|_|) prop (o : obj) =
    match o with
    | :? JObject as o -> o.[prop] |> Option.ofObj
    | _ -> None

let (|Deep|_|) (path : string) (o : obj) =
    let get t p = t |> Option.bind (fun t -> (``|J|_|``) p t)
    match o with
    | :? JToken as t ->
        path.Split('.') |> Array.fold get (Option.ofObj t)
    | _ -> None

... некоторые помощники ...

let jV (t : JToken) = t.Value<string>()
let handle t = jV t |> printfn "single: %s"
let handle2 a b = printfn "(%s, %s)" (jV a) (jV b)

... функция разбора...

let parse o =
    match JsonConvert.DeserializeObject o with
    | null -> printfn "null"
    | J "aa" a -> handle a
    | J "bb" b & J "cc" c -> handle2 b c
    | J "bb" b & J "dd"  _ -> handle b
    | Deep "foo.bar" bar & Deep "hello.world" world -> handle2 bar world
    | Deep "foo.bar" bar -> handle bar
    | o -> printfn "val: %A" o

... и поехали:

parse "null" // null
parse "42" // val: 42L
parse "{ aa: 3.141 }" // single: 3.141
parse "{ bb: 2.718, cc: \"e\" }" // (2.718, e)
parse "{ bb: 2.718, dd: 0 }" // single: 2.718
parse "{ foo: { bar: \"baz\" } }" // single: baz
parse "{ foo: { bar: \"baz\" }, hello: { world: \"F#|>I❤\" } }" // (baz, F#|>I❤)
2 голосов
/ 06 ноября 2019
  1. Чтобы сделать что-то для первого ненулевого значения:
    let j = JObject.Parse x
    let doSomething s = printf "%A" s
    if isNull j then
        ()
    else
        [ j.["aa"]; j.["bb"]; j.["cc"] ]
        |> List.tryFind (fun s -> s |> Option.ofObj |> Option.isSome)
        |> doSomething
Или сделать что-то для каждого ненулевого значения:
    let j = JObject.Parse x
    let doSomething s = printf "%A" s
    if isNull j then
        ()
    else
        [ j.["aa"]; j.["bb"]; j.["cc"] ]
        |> List.choose (fun s -> s |> Option.ofObj)
        |> List.iter doSomething
Или сделать что-то другое (в зависимости от того, какое значение не является нулевым) для первого ненулевого значения:
    let j = JObject.Parse x
    let doSomethingA s = printf "%A" s
    let doSomethingB s = printf "%A" s
    let doSomethingC s = printf "%A" s
    if isNull j then
        ()
    else
        [ 
            j.["aa"], doSomethingA
            j.["bb"], doSomethingB
            j.["cc"], doSomethingC
        ]
        |> List.tryFind (fun (s, _) -> s |> Option.ofObj |> Option.isSome)
        |> Option.iter (fun (s, f) -> f s)
1 голос
/ 06 ноября 2019

Вы можете составить список действий для каждого ключа, чтобы можно было применять логику проверки на ноль для каждого из них.

let j = JObject.Parse x
let doStuff key value = printfn "%s=>%s" key value

Если вы хотите применить doStuff для каждого ключа, который вы можете выполнить итерацию. Это ваш пример, но без остального, поэтому он делает это для каждого присутствующего ключа.

  ["aa", doStuff
   "bb", doStuff
   "cc", doStuff]
  |> List.iter (fun (key,action) -> 
    j.TryGetValue key
    |> snd
    |> Option.ofObj
    |> Option.iter (action key))

Если вы более точно подходите к своему примеру, где вы используете только doStuff для первого присутствующего ключа, вы можете выбрать, чтобы получить только действительные значения,actions.

  ["aa", doStuff
   "bb", doStuff
   "cc", doStuff]
   |> Seq.choose (fun (key,action) ->
      j.TryGetValue key
      |> snd
      |> Option.ofObj
      |> Option.map (fun v -> action key v))
   |> Seq.tryHead

Эта версия также возвращает результат примененного doStuff, если был соответствующий ключ и doStuff вернул значение. Это немного злоупотребляет ленивой природой Seq, чтобы вызывать только первое значение, но вы также можете сопоставить функции вызов функции Seq.tryHead.

1 голос
/ 06 ноября 2019

Вы можете создать активный шаблон для соответствия ненулевым значениям ...

let (|NonNull|_|) = function null -> None | v -> Some v

..., что позволило бы следующее.

if isNull j then
    //do stuff
else
    match j.["aa"], j.["bb"], j.["cc"], j.["dd"] with
    | NonNull aa, _, _, _ -> //do stuff
    | _, NonNull bb, _, _ -> //do stuff
    | _, _, NonNull cc, NonNull dd -> //do stuff
...