我正在尝试将FSharp.Data示例转换为我正在处理的问题的解决方案,但我只是没有走得太远。
给定一个返回json的端点,类似于:
{
Products:[{
Id:43,
Name:"hi"
},
{
Id:45,
Name:"other prod"
}
]
}
如何加载数据,然后只从真实的现有数据中获取Id
?
我不明白如何“模式匹配”可能性:
root.Products
可能不存在/空Id
可能不存在namespace Printio
open System
open FSharp.Data
open FSharp.Data.JsonExtensions
module PrintioApi =
type ApiProducts = JsonProvider<"https://api.print.io/api/v/1/source/widget/products?recipeId=f255af6f-9614-4fe2-aa8b-1b77b936d9d6&countryCode=US">
let getProductIds url =
async {
let! json = ApiProducts.AsyncLoad url
let ids = match json with
| null -> [||]
| _ ->
match json.Products with
| null -> [||]
| _ -> Array.map (fun (x:ApiProducts.Product)-> x.Id) json.Products
return ids
}
答案 0 :(得分:4)
编辑:当我写这个答案时,我并没有完全理解JSON类型提供程序的功能。事实证明,您可以使用示例JSON文档列表填充它,这使您可以处理数据可能存在或不存在的各种情况。这些天我用得很多,所以我不再相信我最初的写作。我将在这里留下原始答案,以防任何人从中获取任何价值。
请参阅我的other answer here on the page,了解我今天如何做到这一点。
虽然类型提供程序很好,但我认为在概念上错误地尝试处理类似JSON的东西,它没有架构,也没有类型安全,就像强类型数据一样。我使用HttpClient,Json.NET和FSharp.Interop.Dynamic来编写这样的查询,而不是使用类型提供程序:
let response = client.GetAsync("").Result
let json = response.Content.ReadAsJsonAsync().Result
let links = json?links :> obj seq
let address =
links
|> Seq.filter (fun l -> l?rel <> null && l?href <> null)
|> Seq.filter (fun l -> l?rel.ToString() = rel)
|> Seq.map (fun l -> Uri(l?href.ToString()))
|> Seq.exactlyOne
其中client
是HttpClient
的实例,而ReadAsJsonAsync
是一个像这样定义的辅助方法:
type HttpContent with
member this.ReadAsJsonAsync() =
let readJson (t : Task<string>) =
JsonConvert.DeserializeObject t.Result
this.ReadAsStringAsync().ContinueWith(fun t -> readJson t)
答案 1 :(得分:2)
如果您对源数据有一定程度的信任,您可能不需要模式匹配来检查它是否为空数组。这样的事情可能会正常工作: -
let getProductIds url =
async {
let! json = ApiProducts.AsyncLoad url
return json.Products |> Seq.map(fun p -> p.Id) |> Seq.cache
}
注意在async {}块中你不应该使用Async.RunSynchronously - 你可以做一个让!将异步等待结果的绑定。
答案 2 :(得分:2)
为类型提供者提供足够的示例来推断这些案例。例如:
[<Literal>]
let sample = """
{
Products:[{
Id:null,
Name:"hi"
},
{
Id:45,
Name:"other prod"
}
]
}
"""
type MyJsonType = JsonProvider<sample>
但请注意,如果json不够规则,它永远不会100%安全
答案 3 :(得分:2)
如果您怀疑数据源可能包含某些缺失值,可以在JsonProvider中设置SampleIsList = true
,并为其提供样本列表,而不是单个示例:
open FSharp.Data
type ApiProducts = JsonProvider<"""
[
{
"Products": [{
"Id": 43,
"Name": "hi"
}, {
"Name": "other prod"
}]
},
{}
]
""", SampleIsList = true>
正如Gustavo Guerra在他的回答中暗示的那样,Products
已经是一个列表,因此您可以提供一个具有Id
(第一个)的产品示例,以及一个示例没有Id
(第二个)。
同样,您可以举例说明Products
完全缺失。由于根对象不包含其他数据,因此这只是空对象:{}
。
JsonProvider
足够聪明,可以将缺少的Products
属性解释为空数组。
由于产品可能有Id
,也可能没有int option
,因此推断此属性的类型为let getProductIds json =
let root = ApiProducts.Parse json
root.Products |> Array.choose (fun p -> p.Id)
。
您现在可以编写一个以JSON字符串作为输入的函数,并为您提供它可以找到的所有ID:
Array.choose
请注意,它使用的是Array.map
而不是Array.choose
,因为Id
会自动选择Some
的{{1}}个值。
您现在可以使用各种值进行测试,看它是否有效:
> getProductIds """{ "Products": [{ "Id": 43, "Name": "hi" }, { "Id": 45, "Name": "other prod" }] }""";;
> val it : int [] = [|43; 45|]
> getProductIds """{ "Products": [{ "Id": 43, "Name": "hi" }, { "Name": "other prod" }] }""";;
> val it : int [] = [|43|]
> getProductIds "{}";;
> val it : int [] = [||]
但它仍然在空输入时崩溃;如果TryParse
有JsonProvider
函数或类似内容,我还没有找到...