简体   繁体   English

F# 与查询结果匹配。 有没有一种优雅的方式来做到这一点?

[英]F# match with query results. Is there an elegant way to do this?

I have an result of a JObject type, from parsing json:通过解析 json,我得到了 JObject 类型的结果:

let j = JObject.Parse x

the code I have to do is like:我必须做的代码是:

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

is there a clean way to do this match?有没有一种干净的方法来做这场比赛?

doing statements like做类似的陈述

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

doesn't seem super clean.看起来不是很干净。 Can this be done better?这可以做得更好吗?

  1. To do something for the first non-null value:为第一个非空值做一些事情:
    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
  1. Or do something for each non-null value:或者为每个非空值做一些事情:
    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
  1. Or do something different (depending on which value is non-null) for the first non-null value:或者为第一个非空值做一些不同的事情(取决于哪个值是非空的):
    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)

If you create an active pattern that returns the matched JToken ...如果您创建一个返回匹配JToken的活动模式 ...

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

you could write something like:你可以这样写:

let handleAA (a : JToken) = ()

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

Update更新

If you need more power, Active Patterns galore...如果你需要更多的力量,Active Patterns galore...

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

... some helpers... ...一些帮手...

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)

... a parse function... ...解析 function...

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

... and off we go: ...然后我们 go:

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❤)

You could create an active pattern to match non-null values...您可以创建一个活动模式来匹配非空值...

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

...which would allow the following. ...这将允许以下内容。

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

You could make a list of actions for each key so you could apply the null checking logic uniformly for each one.您可以为每个键制作一个操作列表,以便您可以为每个键统一应用 null 检查逻辑。

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

If you wanted to apply doStuff for every key you could iterate though.如果你想为每个键应用 doStuff,你可以迭代。 This is your example but without the else so it does it for every key present.这是您的示例,但没有 else ,因此它对存在的每个键都执行此操作。

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

Matching your example more closely where you only doStuff for the first key present might use choose to get only the valid values,actions.更紧密地匹配您的示例,其中您仅 doStuff 出现的第一个键可能使用选择仅获取有效值、操作。

  ["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

This version also returns the result of the applied doStuff if there was a matching key and doStuff returned a value.如果存在匹配的键并且 doStuff 返回了值,则此版本还会返回应用的 doStuff 的结果。 This is abusing the lazy nature of Seq a little bit to only call the first value but you could also map to a function an call the result of Seq.tryHead.这有点滥用 Seq 的惰性,只调用第一个值,但您也可以 map 到 function 调用 Seq.tryHead 的结果。

Option.ofObj will turn null -> None and v -> Some v Option.ofObj 将变成 null -> None 和 v -> Some v

Since JObject is enumerable you can just map over it由于 JObject 是可枚举的,因此您只需 map 就可以了

let j = JObject.Parse x |> Seq.map Option.ofObj
// j.["aa"] is an Option type now, map will only multiply the value by 3 if it exists
// otherwise it will return none. 
let aa = j.["aa"] |> Option.map (fun a -> a * 3)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM