Fix the JsonMappingException – HashMap vs START_ARRAY token
1. Introduction
In this example. I will create a simple Java project that demonstrates how to fix the JsonMappingException problem: Can not deserialize instance of java.util.HashMap
out of START_ARRAY token. The com.fasterxml.jackson.databind.JsonMappingException is a checked exception used to signal fatal problems when mapping content and provides the relevant path of references to help in troubleshooting.
2. Setup
In this step, I will create a gradle project along with the jackson-databind and Junit5 libraries.
build.gradle
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | plugins { id 'java' id 'org.springframework.boot' version '3.4.1' id 'io.spring.dependency-management' version '1.1.7' } group = 'org.zheng.demo' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'com.fasterxml.jackson.core:jackson-databind' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() } |
3. JsonMappingException Root Cause
JSON (JavaScript Object Notation) is a lightweight, text-based string that supports two primary data structures: Objects and Arrays.
- JSON object
- is enclosed in curly braces (
{}
). - is an unordered collection of key-value pairs. Each key-value pair is separated by a comma (
,
) without the trailing comma. The key is a string and must be wrapped in double quotes (" "
). Each key is followed by a colon(:
) and then its corresponding value. A value in JSON can be a string (enclosed in double quotes" "
), a number (either an integer or a floating-point number), a boolean (true
orfalse
), anull
value (null
), a JSON object (nested objects), or a JSON array (nested arrays).
- is enclosed in curly braces (
- JSON array
- Arrays are enclosed in square brackets (
[]
). - is an ordered list of values.
- Each value in the array is separated by a comma (
,
).
- Arrays are enclosed in square brackets (
The java.util.HashMap is a Java object that is used for storing key-value pairs. Therefore, the JsonMappingException
is thrown when parsing the JSON array string as a HashMap
object.
4. Jackson ObjectMapper Configuration
In this step, I will create a JacksonConfiguration.java
to configure an ObjectMapper
spring bean.
JacksonConfiguration.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | package org.zheng.demo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.fasterxml.jackson.databind.ObjectMapper; @Configuration public class JacksonConfiguration { @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } } |
5. MapperService to Fix the JsonMappingException
In this step, I will create a MapperService.java
with two methods.
readAsHashMap
: reads a JSON Object string as aHashMap
object.readAsMaps
: reads a JSON Array string as aList
ofMap
objects.
MapperService.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package org.zheng.demo.data; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @Service public class MapperService { private final ObjectMapper ob; public MapperService(ObjectMapper obmapper) { this .ob = obmapper; } @SuppressWarnings ( "unchecked" ) public HashMap<String, Object> readAsHashMap( final String jsonObjectString) throws JsonProcessingException { return ob.readValue(jsonObjectString, HashMap. class ); } public List<Map<String, Object>> readAsMaps( final String jsonArrayString) throws JsonProcessingException { return ob.readValue(jsonArrayString, new TypeReference<List<Map<String, Object>>>() { }); } } |
5.1 Fix the JsonMappingException Tests
I will also create a MapperServiceTest.java
that shows JsonMappingException
is thrown when mapping a JSON array string to a HashMap
object.
MapperServiceTest.java
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 | package org.zheng.demo.data; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @SpringBootTest class MapperServiceTest { private static final String UNEXPECTED_CHARACTER = "Unexpected character" ; @Autowired private MapperService testClass; private final String invalidJsonKeyNotEnclosed = "" " { stringKey: "valueString" } "" "; private final String invalidJsonTraillingComma = "" " { stringKey: "valueString" , } "" "; private final String validListMapJson = "" " [ { "stringKey" : "valueString" }, { "anotherStringKey" : "val2" } ] "" "; private final String validMapJson = "" " { "stringKey" : "valueString" , "anotherStringKey" : "val2" , "numberKey" : 133 , "booleanKey" : true } "" "; @Test void test_readAsHashMap_for_validMapJson() { try { HashMap<String, Object> readData = testClass.readAsHashMap(validMapJson); assertEquals( "valueString" , readData.get( "stringKey" )); assertEquals( "val2" , readData.get( "anotherStringKey" )); assertEquals( 133 , (Integer) readData.get( "numberKey" )); assertTrue((Boolean) readData.get( "booleanKey" )); assertNull(readData.get( "badKey" )); } catch (JsonProcessingException e) { e.printStackTrace(); } } @Test void test_readAsHashMap_throw_JsonParseException_for_invalidJsonKeyNotEnclosed() { JsonParseException exception = assertThrows(JsonParseException. class , () -> { testClass.readAsHashMap(invalidJsonKeyNotEnclosed); }); assertTrue(exception.getMessage().contains(UNEXPECTED_CHARACTER)); } @Test void test_readAsHashMap_throw_JsonParseException_for_invalidJsonTraillingComma() { JsonParseException exception = assertThrows(JsonParseException. class , () -> { testClass.readAsHashMap(invalidJsonTraillingComma); }); assertTrue(exception.getMessage().contains(UNEXPECTED_CHARACTER)); } @Test void test_readAsHashMap_throw_JsonMappingException_for_validListMapJson() { JsonMappingException exception = assertThrows(JsonMappingException. class , () -> { testClass.readAsHashMap(validListMapJson); }); assertTrue(exception.getMessage().contains( "JsonToken.START_ARRAY" )); } @Test void test_readAsMaps_for_validListMapJson() { try { List<Map<String, Object>> readData = testClass.readAsMaps(validListMapJson); assertEquals( "valueString" , readData.get( 0 ).get( "stringKey" )); assertEquals( "val2" , readData.get( 1 ).get( "anotherStringKey" )); assertNull(readData.get( 0 ).get( "badKey" )); } catch (JsonProcessingException e) { e.printStackTrace(); } } } |
- Line 30: create an invalid JSON string as its key is not enclosed with double quotes.
- Line 36: create an invalid JSON string as it has a trailing comma.
- Line 41: create a valid JSON Array string as it starts with [].
- Line 52: create a valid JSON Object string as it starts with {}.
- Line 61:
test_readAsHashMap_for_validMapJson
verifies reading a valid JSON map string to aHashMap
is passed. - Line 76:
test_readAsHashMap_throw_JsonParseException_for_invalidJsonKeyNotEnclosed
verifies aJsonParseException
is thrown when the JSON key is not enclosed with double quotes. - Line 84:
test_readAsHashMap_throw_JsonParseException_for_invalidJsonTraillingComma
verifies aJsonParseException
is thrown when the JSON is included a trailing comma. - Line 92:
test_readAsHashMap_throw_JsonMappingException_for_validListMapJson
confirms thatJsonMappingException
is thrown when parsing a JSON array into aHashMap
object. - Line 100:
test_readAsMaps_for_validListMapJson
verified that JSON Array of maps string can be mapped to aList
ofMap
objects.
Ran the unit tests and captured the test results:
6. Conclusion
In this example, I demonstrated how to fix the JsonMappingException
via JUnit tests by mapping the JSON array string to a list of HashMap
objects. I also explained why the JsonMappingException
is thrown when parsing a JSON array as a HashMap
object.
7. Download
This was an example of a gradle project which showed JsonMappingException
is thrown when parsing JSON array as a HashMap
and its solution.
You can download the full source code of this example here: Fix the JsonMappingException – HashMap vs START_ARRAY token