【问题标题】:JSONObject to ArrayList square brackets missingJSONObject 到 ArrayList 方括号丢失
【发布时间】:2020-11-28 00:20:30
【问题描述】:

我正在尝试将 https://api.ratesapi.io/api/latest 的费率转换为自定义 Currency 类的 ArrayList<Currency>

public class Currency {
    private String shortName;
    private double rate;
    ...
}

JSON 看起来像:

{"base":"EUR","rates":{"GBP":0.90033,"HKD":9.1786,"IDR":17304.0,
 "ILS":4.0309,"DKK":7.45,"INR":88.765,"CHF":1.0759,"MXN":26.615,
 "CZK":26.202,"SGD":1.6236,"THB":36.832,"HRK":7.468,"MYR":4.9604,
 "NOK":10.6538,"CNY":8.2325,"BGN":1.9558,"PHP":58.136,"SEK":10.3165,
 "PLN":4.4073,"ZAR":20.7655,"CAD":1.5748,"ISK":160.2,"BRL":6.334,
 "RON":4.836,"NZD":1.7828,"TRY":8.5853,"JPY":124.96,"RUB":86.9321,
 "KRW":1404.99,"USD":1.1843,"HUF":346.23,"AUD":1.6492},"date":"2020-08-06"}

使用org.json,我设法将数据放入JSONObject

JSONObject obj = new JSONObject(getJSON("https://api.ratesapi.io/api/latest"));

据我了解,现在的正常程序是将JSONObject 转换为JSONArray。但是尝试:

JSONArray jsonArray = obj.getJSONArray("rates");

失败并显示错误消息:

Exception in thread "main" org.json.JSONException: JSONObject["rates"]
is not a JSONArray.

我该如何解决这个错误,或者有没有其他方法可以从 JSON 中创建一个 ArrayList?

我怀疑问题出在 JSON 字符串中缺少方括号。

【问题讨论】:

标签: java json arraylist


【解决方案1】:

如果您查看 API 返回的 JSON,您会得到一个 JSON 对象:

{"base":"EUR","rates":{"GBP":0.90033,"HKD":9.1786, ... },"date":"2020-08-06"}

你可能想做这样的事情:

JSONObject obj = new JSONObject(getJSON("https://api.ratesapi.io/api/latest"));
JSONObject rates = obj.getJSONObject("rates");
final Iterator<String> keys = rates.keys();
while (keys.hasNext()) {
  final String key = keys.next();
  final Currency currency = new Currency(key, rates.getDouble(key));
  // do something with the Currency
}

【讨论】:

  • @user1583209 当您使用货币数据时,我建议不要使用 double 进行计算。而是使用BigDecimal 来避免舍入问题。像new BigDecimal(rates.getString(key)) 这样的东西可能会做
【解决方案2】:

对象“rates”不是 JSONArray,是 JSONObject。

所以你必须 obj.getJSONObject(rates");then 使用 map 方法迭代 JSONObject 的字段(例如使用 keySet() )

【讨论】:

    【解决方案3】:

    使用 Jackson 库和 Lombok 的工作解决方案可能如下:

    import com.fasterxml.jackson.annotation.JsonFormat;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
    import lombok.*;
    import java.util.*;
    import java.util.stream.Collectors;
    
    public class CcyApiParser {
        @Getter
        @Setter
        @AllArgsConstructor
        @NoArgsConstructor
        @ToString
        public static class Currency {
            private String shortName;
            private double rate;
        }
    
        @Getter
        @Setter
        public static class RatesApiResponse {
            private String base;
            private Map<String, Double> rates;
            @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
            private LocalDate date;
        }
    
    
        public static void main(String[] args) throws IOException {
    
            ObjectMapper mapper = new ObjectMapper()
                    .registerModule(new JavaTimeModule()); // to parse date
    
            URL apiUrl = new URL("https://api.ratesapi.io/api/latest");
        
            // read proper api response
            RatesApiResponse rates = mapper.readValue(apiUrl, RatesApiResponse.class);
    
            // convert inner rates into list of Currency objects
            List<Currency> ccys = rates.getRates().entrySet().stream()
                    .map(e -> new Currency(e.getKey(), e.getValue()))
                    .collect(Collectors.toList());
    
            ccys.forEach(ccy -> System.out.printf("%s=%s%n", ccy.getShortName(), ccy.getRate()));
        }
    }
    

    输出

    GBP=0.90033
    HKD=9.1786
    IDR=17304.0
    ILS=4.0309
    ... etc.
    

    更新

    还可以自定义 RatesApiResponse 的反序列化并将 "rates" 的映射移动到此类中以立即转换为货币列表。

        @Getter
        @Setter
        public static class RatesApiResponse {
            private String base;
            @JsonProperty(access = JsonProperty.Access.READ_ONLY)
            private List<Currency> ccys;
    
            @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
            private LocalDate date;
    
            // no getter for rates
            // this customized setter for the map of rates converts into a list
            @JsonProperty("rates")
            public void setRates(Map<String, Double> rates) {
                ccys = rates.entrySet().stream()
                        .map(e -> new Currency(e.getKey(), e.getValue()))
                        .collect(Collectors.toList());
            }
        }
    
    // Updates in the test method
    RatesApiResponse rates = mapper.readValue(src, RatesApiResponse.class);
    
    rates.getCcys().forEach(ccy -> System.out.printf("%s=%s%n", ccy.getShortName(), ccy.getRate()));
    

    【讨论】:

      【解决方案4】:

      您可以使用ObjectMapper 类将json 从一些URL 转换为某种对象。在这种情况下(如果json 结构始终相同)它可以是Map&lt;String, Object&gt;

      ObjectMapper mapper = new ObjectMapper();
      URL url = new URL("https://api.ratesapi.io/api/latest");
      Map<String, Object> map = mapper.readValue(url, Map.class);
      
      System.out.println(map);
      // {base=EUR, rates={GBP=0.90373, HKD=9.1585, ... , AUD=1.6403}, date=2020-08-07}
      

      然后您可以获取内部rates 映射,并(如果需要)使用java stream api 将其转换为列表:

      Map<String, Double> rates = (Map<String, Double>) map.get("rates");
      
      System.out.println(rates); // {GBP=0.90373, HKD=9.1585, ... , AUD=1.6403}
      

      Map&lt;String, Object&gt; 转换为ArrayList&lt;Currency&gt;

      ArrayList<Currency> list = rates.entrySet().stream()
          .map(entry -> new Currency(entry.getKey(), entry.getValue()))
          .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
      
      System.out.println(list); // [GBP=0.90373, HKD=9.1585, ... , AUD=1.6403]
      

      注意: 添加一个带有两个字段shortNamerate 的构造函数;
      注意:重写toString方法如下:shortName + "=" + rate;


      Maven 依赖:

      <dependency>
          <groupId>com.fasterxml.jackson.dataformat</groupId>
          <artifactId>jackson-dataformat-xml</artifactId>
          <version>2.11.2</version>
      </dependency>
      

      另见:«Formatting Json Response into an Array Java»

      【讨论】:

        【解决方案5】:

        线程“main”中的异常 org.json.JSONException: JSONObject["rates"] 不是 JSONArray。

        您收到此错误是因为rates 不是数组形式。它只是一个像basedate 这样的元素,但看起来像一个数组。从 JSON 字符串中获取它,就像从中获取 basedate 一样,然后对其进行处理以创建所需的 List&lt;Currency&gt;

        下面给出的是工作代码,并在代码中以 cmets 形式添加了解释:

        import java.io.BufferedReader;
        import java.io.IOException;
        import java.io.InputStream;
        import java.io.InputStreamReader;
        import java.io.Reader;
        import java.net.URL;
        import java.net.URLConnection;
        import java.nio.charset.Charset;
        import java.util.ArrayList;
        import java.util.List;
        
        import org.json.JSONException;
        import org.json.JSONObject;
        
        class Currency {
            private String shortName;
            private double rate;
        
            public Currency(String shortName, double rate) {
                this.shortName = shortName;
                this.rate = rate;
            }
        
            @Override
            public String toString() {
                return shortName + ":" + rate;
            }
        }
        
        public class Main {
        
            public static JSONObject getJSON(String url) throws IOException, JSONException {
                // Create a URLConnection for the given URL
                URLConnection connection = new URL(url).openConnection();
        
                // Add header to avoid 403 Forbidden HTTP status code
                connection.addRequestProperty("User-Agent",
                        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:79.0) Gecko/20100101 Firefox/79.0" + "");
        
                StringBuilder jsonStr = new StringBuilder();
        
                // Get InputStream from connection and read the response
                try (InputStream is = connection.getInputStream();) {
                    Reader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
        
                    int ch;
                    while ((ch = reader.read()) != -1) {
                        jsonStr.append((char) ch);
                    }
                }
                return new JSONObject(jsonStr.toString());
            }
        
            public static void main(String[] args) throws IOException, JSONException {
                JSONObject jsonObj = getJSON("https://api.ratesapi.io/api/latest");
        
                // Get rates from jsonObj
                String rates = jsonObj.get("rates").toString();
        
                // Remove {, }, and " from the string
                String[] keyValArr = rates.replaceAll("[\\{\\\"}]", "").split(",");
        
                // List object to hold Currency objects
                List<Currency> list = new ArrayList<>();
        
                for (String keyVal : keyValArr) {
                    // Split each key:value string on ':'
                    String[] curRate = keyVal.split(":");
        
                    // Add Currency object to List
                    list.add(new Currency(curRate[0], Double.parseDouble(curRate[1])));
                }
        
                // Display list
                list.forEach(System.out::println);
            }
        }
        

        输出:

        CHF:1.0804
        HRK:7.4595
        MXN:26.5127
        ...
        ...
        ...
        NZD:1.7786
        BRL:6.3274
        

        【讨论】:

        • 注意:我使用了 Firefox 插件 HTTP Header Live 来获取标题 User-Agent 的值。
        猜你喜欢
        • 2019-12-31
        • 2016-01-03
        • 2013-05-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-03
        相关资源
        最近更新 更多