JSONObject 到 ArrayList 方括号丢失

Posted

技术标签:

【中文标题】JSONObject 到 ArrayList 方括号丢失【英文标题】:JSONObject to ArrayList square brackets missing 【发布时间】: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 字符串中缺少方括号。

【问题讨论】:

这能回答你的问题吗? simplest way to read json from a URL in java @KaustubhKhare 虽然相关,但该欺骗不会帮助 OP,因为他们要解析的类与 JSON 内容的结构不匹配。 这更接近***.com/q/22687771/1690217 问题是rates 是一个具有与您的Currency 结构匹配的属性 的对象,您需要转换rates对象到它的属性数组中。 【参考方案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 的值。

以上是关于JSONObject 到 ArrayList 方括号丢失的主要内容,如果未能解决你的问题,请参考以下文章

从 JSON 检索数据但无法从 JSONobject 访问 Arraylist

循环以在 JAVA 中的 JSONObject 内获取相同的对象 N 次

JSONObject 不可序列化?

JSONObject和JSONArray的简单使用

java中如何用json格式发送并接受arrayList?

java范型集合中的成员排序