与嵌套 json 响应属性的模式匹配

pattern matching with nested json response attributes

我正在尝试找到一种更优雅的方法来在从 get_public_request 函数获得响应后设置变量。我在以下代码示例中引用 var1var2

def get_prices(item) do
    url = item_path(item)
    response = get_public_request(url)
    var1 = response["item"]["buy"]
    var2 = response["item"]["sell"]
end

def get_public_request(url) do
    HTTPoison.start
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        Poison.decode!(body)
    ...
    end
end

我得到的响应(在 Poison.decocode 之后)如下所示:

%{"at" => 1536333060, "item" => %{"buy" => "8971.71", "area" => "A16", "sell" => "9019.89"}}

在您提供的代码中,您在 get_prices 中需要的一切都是这两个参数。选项是 return 他们明确来自 get_public_request:

def get_public_request(url) do
  HTTPoison.start
  case HTTPoison.get(url) do
    {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
      with %{"item" => %{"buy" => buy, "sell" => sell}} <- Poison.decode!(body),
        do: {buy, sell}
    ...
    end
end

Kernel.SpecialForms.with/1 将 return 任何未匹配的 RHO,因此如果我们进入 do,我们就有了我们需要的东西。称其为:

def get_prices(item) do
  url = item_path(item)
  {var1, var2} = get_public_request(url)
end

如果您希望它们不总是出现在响应中,请使用 Kernel.SpecialForms.case/2:

def get_public_request(url) do
  HTTPoison.start
  case HTTPoison.get(url) do
    {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
      case Poison.decode!(body) do
        %{"item" => %{"buy" => buy, "sell" => sell}} -> {buy, sell}
        %{"item" => %{"buy" => buy}} -> {buy, nil}
        %{"item" => %{"sell" => sell}} -> {nil, sell}
        _ -> {nil, nil}
      end
    ...
    end
end

像@mudasobwa 一样,我会在 get_public_request 中进行数据提取,但使用其他两种语法之一:

地图上的模式匹配

def get_prices(item), do:
    %{"buy":var1, "sell": var2} = item_path(item) |> get_public_request do 

def get_public_request(url) do
    HTTPoison.start
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        Poison.decode!(body)["item"] |> Map.take(["buy", "sell"])
    ...
    end
end

注意:如果缺少 "buy" 或 "sell" 两个字段之一,此解决方案将在模式匹配期间崩溃。你可以避免使用 case.

元组上的模式匹配

def get_prices(item), do: {var1, var2} = item_path(item) |> get_public_request
def get_public_request(url) do
    HTTPoison.start
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        Poison.decode!(body)["item"] |> (&({&1["buy"],&1["sell"]}).()
    ...
    end
end

如果要处理字段中是否有数据的情况,可以添加以下检查:

Poison.decode!(body)["item"] |> (&({Map.has_key?(&1,"buy") && &1["buy"] || nil, Map.has_key?(&1,"sell") && &1["sell"] || nil}).()