3

我正在尝试将一些 JS 转换为 Reason,在此过程中我需要输入 JSON 响应并检查对象中是否存在键。

这是我当前的代码:

let api_key = "";
let api_url = "http://ws.audioscrobbler.com/2.0";
let method = "user.getRecentTracks";
let user = "montogeek";

type trackAttr = {
  nowplaying: bool
};

type artistT = {
  text: string
}

type trackT = {
  attr: trackAttr,
  name: string,
  artist: artistT
};

type recentTrackT = {
  track: array(Js.Dict.t(trackT))
};

type response = {
  recenttracks: recentTrackT
};

Js.Promise.(Fetch.fetch(api_url ++ "?method=" ++ method ++ "&" ++ user ++ "=" ++ user ++ "&limit=1&format=json&api_key=" ++ api_key)
    |> then_(Fetch.Response.json)
    |> then_(json: response => {
      let lasttrack = json.recenttracks.track[0];

      let online = switch (Js.Dict.get(lasttrack, "attr")) {
      | None => false
      | Some(track) => track.attr.nowplaying
      };
      let info = online ? "Enjoying" ++ lasttrack.name ++ "by " ++ lasttrack.artist["#text"] ++ "}" : "";

      { online, info }
    }));

目前我收到此错误:

We've found a bug for you!
  /Users/montogeek/Infinite/Lov/online/src/lastfm.re 37:41-49

  35 ┆ | Some(track) => track.attr.nowplaying
  36 ┆ };
  37 ┆ let info = online ? "Enjoying" ++ lasttrack.name ++ "by " ++ lasttrack
       .artist["#text"] ++ "}" : "";
  38 ┆
  39 ┆ { online, info }

  This has type:
    Js.Dict.t(trackT) (defined as Js.Dict.t(trackT))
  But somewhere wanted:
    trackT

我不能删除Js.Dict.t类型,因为Js.Dict.get不喜欢它。

如何键入响应以使其正常工作?

谢谢!

4

1 回答 1

6

您可能想尝试以较小的增量构建它,因为您一直在这里给自己挖一个非常深的洞。

首先,您是根据记录类型而不是 JS 对象类型来描述您的 JSON 响应。

这是一个记录类型:

type  t = { foo: string };

这是一个 JS 对象类型:

type t = {. "foo": string };

一个微妙但非常显着的差异。

其次Js.Dict.t(trackT)不是您认为的那样(尽管我也不清楚这到底是什么)。它描述了一个用作散列的 JS 对象,具有 type 的键和 type 的StringtrackT。这看起来像是试图解决第一个问题,但只会使漏洞更深,所以也许只是恢复它。

response第三,您不能只通过注释来断言 JSON 响应具有类型。这是一个健全的类型系统,它只会给你一个类型错误。如果你想绕过类型系统(这几乎总是一个坏主意),你必须更加明确。

还有一些其他的小错误,但这超出了这个范围。

所以你应该做的是以下两件事之一:

正确的方法是使用诸如bs-json之类的库将 JSON 响应解码为您已经定义的记录类型,在应该完成的边界处验证数据的形状,然后继续使用该记录正常类型:

/* assume record types are defined above */

module Decode = {
  open Json.Decode;

  let trackAttr = json => {
    nowplaying: json |> field("nowplaying", bool)
  };

  let artist = json => {
    text: json |> field("text", string)
  }

  let track = json => {
    attr: json |> optional(field("attr", trackAttr)),
    name: json |> field("name", string),
    artist: json |> field("artist", artist)
  };

  let recentTrack = json => {
    track: json |> field("track", array(track))
  };

  let response = json => {
    recenttracks: json |> field("recenttracks", recentTrack)
  };
};

Js.Promise.(Fetch.fetch(api_url ++ "?method=" ++ method ++ "&" ++ user ++ "=" ++ user ++ "&limit=1&format=json&api_key=" ++ api_key)
    |> then_(Fetch.Response.json)
    |> then_(json => {
      let response = Decode.response(json);
      let lasttrack = response.recenttracks.track[0];
      ...
    }));

快速而肮脏的方法是将记录类型转换为 JS 对象类型,然后断言数据具有该形状,从而绕过类型系统。如果它没有您期望的形状,您可能会在任何地方遇到运行时错误,并且需要手动追踪问题的根源。这可能是这样的:

type trackAttr = {.
  "nowplaying": bool
};

type artistT = {.
  "text": string
}

type trackT = {.
  "attr": trackAttr,
  "name": string,
  "artist": artistT
};

type recentTrackT = {.
  "track": array(trackT)
};

type response = {.
  "recenttracks": recentTrackT
};

external unsafeCastJsonAsResponse : Js.Json.t => response = "%identity";

Js.Promise.(Fetch.fetch(api_url ++ "?method=" ++ method ++ "&" ++ user ++ "=" ++ user ++ "&limit=1&format=json&api_key=" ++ api_key)
    |> then_(Fetch.Response.json)
    |> then_(json => {
      let response = unsafeCastJsonAsResponse(json);
      let lasttrack = response##recenttracks##track[0];
      ...
    }));
于 2018-05-23T17:44:54.520 回答