使用Jackson ObjectMapper解析无命名嵌套JSON数组时遇输入结束错误的解决方法
Let's start with the obvious fix first—your test case's JSON string is invalid! You've got an extra opening bracket and a missing closing one, which is exactly why Jackson is throwing that "expected close marker for Array" error. Once we fix that, we'll go over a few solid ways to parse this array-heavy JSON properly.
First: Correct Your Test JSON String
Your target JSON from the API is a 2-level nested array (an outer array holding one K-line data array), but your KLINES string creates a 3-level array and doesn't close it off. Here's the corrected version that matches the API's format:
private static final String KLINES = "[\n" + " [\n" + " 1499040000000,\n" + " \"0.01634790\",\n" + " \"0.80000000\",\n" + " \"0.01575800\",\n" + " \"0.01577100\",\n" + " \"148976.11427815\",\n" + " 1499644799999,\n" + " \"2434.19055334\",\n" + " 308,\n" + " \"1756.87402397\",\n" + " \"28.46694368\",\n" + " \"17928899.62484339\"\n" + " ]\n" + "]";
This removes the extra nested [ and adds the missing closing ], making the JSON valid.
Now: Parse the JSON Properly
Once your JSON is valid, you've got a few options depending on whether you want flexibility or type safety:
Option 1: Use JsonNode for Flexible Access
Your existing test code uses JsonNode, which works great once the JSON is fixed. You can grab elements by their index since each K-line is an ordered array:
@Test public void readKlinesWithJsonNode() throws JsonProcessingException { ObjectMapper objectMapper = JSonHelper.createObjectMapper(); JsonNode rootArray = objectMapper.readTree(KLINES); // Get the first (and only) K-line array from the root JsonNode klineData = rootArray.get(0); // Pull values by index (matches the comments in your API JSON) long openTime = klineData.get(0).asLong(); String openPrice = klineData.get(1).asText(); double highPrice = klineData.get(2).asDouble(); // ... repeat for low, close, volume, etc. }
Option 2: Map to a Custom Kline Class (Type-Safe)
For cleaner, type-safe code, create a custom class to represent the K-line data. Use Jackson's @JsonPropertyOrder to map the array indices to class fields:
import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonPropertyOrder({ "openTime", "open", "high", "low", "close", "volume", "closeTime", "quoteAssetVolume", "numberOfTrades", "takerBuyBaseAssetVolume", "takerBuyQuoteAssetVolume", "ignore" }) public class Kline { private long openTime; private double open; private double high; private double low; private double close; private double volume; private long closeTime; private double quoteAssetVolume; private int numberOfTrades; private double takerBuyBaseAssetVolume; private double takerBuyQuoteAssetVolume; private double ignore; // Add getters and setters for all fields (or use Lombok to skip boilerplate) public long getOpenTime() { return openTime; } public void setOpenTime(long openTime) { this.openTime = openTime; } public double getOpen() { return open; } public void setOpen(double open) { this.open = open; } // ... implement the rest of the getters/setters }
Then parse the JSON into a List<Kline> using TypeReference:
import com.fasterxml.jackson.core.type.TypeReference; @Test public void readKlinesToCustomClass() throws JsonProcessingException { ObjectMapper objectMapper = JSonHelper.createObjectMapper(); List<Kline> klines = objectMapper.readValue(KLINES, new TypeReference<List<Kline>>() {}); // Access the first K-line Kline firstKline = klines.get(0); System.out.println("Open Time: " + firstKline.getOpenTime()); System.out.println("Open Price: " + firstKline.getOpen()); }
Option 3: Quick and Dirty: Map to List<List<Object>>
If you don't want to create a custom class, you can parse directly into a nested list. Just be careful with type casting:
import com.fasterxml.jackson.core.type.TypeReference; @Test public void readKlinesToList() throws JsonProcessingException { ObjectMapper objectMapper = JSonHelper.createObjectMapper(); List<List<Object>> klineList = objectMapper.readValue(KLINES, new TypeReference<List<List<Object>>>() {}); List<Object> firstKline = klineList.get(0); long openTime = (long) firstKline.get(0); String openPrice = (String) firstKline.get(1); // ... cast other values to their appropriate types }
Pro Tips
- Validate JSON first: If you hit parsing errors, use a tool like JSONLint to check if the JSON is valid—truncated or malformed JSON is the #1 cause of "end-of-input" errors.
- Handle API inconsistencies: If the external API sometimes returns truncated JSON (not just your test case), you can enable
JsonParser.Feature.ALLOW_TRAILING_COMMAor write a custom deserializer to handle partial data, but first confirm the API is supposed to return valid JSON. - Use
@JsonFormatif needed: For date/time fields (like open/close time), you can add@JsonFormatto the class fields to automatically convert timestamps toInstantorDateobjects.
内容的提问来源于stack exchange,提问作者Stefan

