Using the curl command:
curl -u 591bf65f50057469f10b5fd9:0cf17f9b03d056ds0e11e48497e506a2 https://backend.tdk.com/api/devicetypes/59147fd79e93s12e61499ffe/messages
I am getting a JSON response:
{"data":[{"device":"18SE62","time":1494516023,"data":"3235","snr":"36.72",...
I save the response on a txt file and parse it using jackson, and everything is fine
ObjectMapper mapper = new ObjectMapper();
File f = new File(getClass().getResource
("/result.json").getFile());
MessageList messageList = mapper.readValue(f, MessageList.class);
and I assume I should get the same result using RestTemplate but that's not the case
RestTemplate restTemplate = new RestTemplate();
MessageList messageList =
restTemplate.getForObject("http://592693f43c87815f9b8145e9:f099c85d84d4e325a2186c02bd0caeef#backend.tdk.com/api/devicetypes/591570373c87894b4eece34d/messages", MessageList.class);
I got an error instead
Exception in thread "main" org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.tdk.domain.backend.MessageList] and content type [text/html;charset=iso-8859-1]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
at com.tdk.controllers.restful.client.RestTemplateExample.main(RestTemplateExample.java:27)
I tried to set the contentType:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
MessageList messageList =
restTemplate.getForObject(url, entity, MessageList.class);
but then I got a compilation error
The method getForObject(String, Class<T>, Object...) in the type RestTemplate is not applicable for the arguments (String, HttpEntity<String>,
Class<MessageList>)
I also tried to add a the Jackson Message converter
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
//Add the Jackson Message converter
messageConverters.add(new MappingJackson2HttpMessageConverter());
//Add the message converters to the restTemplate
restTemplate.setMessageConverters(messageConverters);
MessageList messageList =
restTemplate.getForObject(url, MessageList.class);
But then I got this error:
Exception in thread "main" org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.tdk.domain.backend.MessageList] and content type [text/html;charset=iso-8859-1]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
at com.tdk.controllers.restful.client.RestTemplateExample.main(RestTemplateExample.java:51)
I also tried adding the class
#Configuration
#EnableWebMvc
public class MvcConf extends WebMvcConfigurationSupport {
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
addDefaultHttpMessageConverters(converters);
}
#Bean
MappingJackson2HttpMessageConverter converter() {
MappingJackson2HttpMessageConverter converter
= new MappingJackson2HttpMessageConverter();
return converter;
}
}
but I got the error:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.tdk.domain.backend.MessageList] and content type [text/html;charset=iso-8859-1]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
The main problem here is content type [text/html;charset=iso-8859-1] received from the service, however the real content type should be application/json;charset=iso-8859-1
In order to overcome this you can introduce custom message converter. and register it for all kind of responses (i.e. ignore the response content type header). Just like this
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
//Add the Jackson Message converter
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// Note: here we are making this converter to process any kind of response,
// not only application/*json, which is the default behaviour
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);
While the accepted answer solved the OP's original problem, most people finding this question through a Google search are likely having an entirely different problem which just happens to throw the same no suitable HttpMessageConverter found exception.
What happens under the covers is that MappingJackson2HttpMessageConverter swallows any exceptions that occur in its canRead() method, which is supposed to auto-detect whether the payload is suitable for json decoding. The exception is replaced by a simple boolean return that basically communicates sorry, I don't know how to decode this message to the higher level APIs (RestClient). Only after all other converters' canRead() methods return false, the no suitable HttpMessageConverter found exception is thrown by the higher-level API, totally obscuring the true problem.
For people who have not found the root cause (like you and me, but not the OP), the way to troubleshoot this problem is to place a debugger breakpoint on onMappingJackson2HttpMessageConverter.canRead(), then enable a general breakpoint on any exception, and hit Continue. The next exception is the true root cause.
My specific error happened to be that one of the beans referenced an interface that was missing the proper deserialization annotations.
UPDATE FROM THE FUTURE
This has proven to be such a recurring issue across so many of my projects, that I've developed a more proactive solution. Whenever I have a need to process JSON exclusively (no XML or other formats), I now replace my RestTemplate bean with an instance of the following:
public class JsonRestTemplate extends RestTemplate {
public JsonRestTemplate(
ClientHttpRequestFactory clientHttpRequestFactory) {
super(clientHttpRequestFactory);
// Force a sensible JSON mapper.
// Customize as needed for your project's definition of "sensible":
ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule())
.configure(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter() {
public boolean canRead(java.lang.Class<?> clazz,
org.springframework.http.MediaType mediaType) {
return true;
}
public boolean canRead(java.lang.reflect.Type type,
java.lang.Class<?> contextClass,
org.springframework.http.MediaType mediaType) {
return true;
}
protected boolean canRead(
org.springframework.http.MediaType mediaType) {
return true;
}
};
jsonMessageConverter.setObjectMapper(objectMapper);
messageConverters.add(jsonMessageConverter);
super.setMessageConverters(messageConverters);
}
}
This customization makes the RestClient incapable of understanding anything other than JSON. The upside is that any error messages that may occur will be much more explicit about what's wrong.
I was having a very similar problem, and it turned out to be quite simple; my client wasn't including a Jackson dependency, even though the code all compiled correctly, the auto-magic converters for JSON weren't being included. See this RestTemplate-related solution.
In short, I added a Jackson dependency to my pom.xml and it just worked:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
One way to debug this issue is to first take the response as a String.class and then use
Gson().fromJson(StringResp.body(), MyDTO.class)
It will most likely still fail, but this time it will throw the fields that caused the error in the first place. Following the modification, we can resume using the previous approach as before.
ResponseEntity<String> respStr = restTemplate.exchange(URL,HttpMethod.GET, entity, String.class);
Gson g = new Gson();
The following step will generate an error with the fields that are causing the problem.
MyDTO resp = g.fromJson(respStr.getBody(), MyDTO.class);
I don't have the error message with me, but it will point to the problematic field and explain why. Resolve those and try the previous approach again.
If the above response by #Ilya Dyoshin didn't still retrieve,
try to get the response into a String Object.
(For my self thought the error got solved by the code snippet by Ilya, the response retrieved was a failure(error) from the server.)
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
ResponseEntity<String> st = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
And Cast to the ResponseObject DTO (Json)
Gson g = new Gson();
DTO dto = g.fromJson(st.getBody(), DTO.class);
In my case #Ilya Dyoshin's solution didn't work: The mediatype "*" was not allowed.
I fix this error by adding a new converter to the restTemplate this way during initialization of the MockRestServiceServer:
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =
new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(
Arrays.asList(
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_OCTET_STREAM));
restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
mockServer = MockRestServiceServer.createServer(restTemplate);
(Based on the solution proposed by Yashwant Chavan on the blog named technicalkeeda)
JN Gerbaux
You need to create your own converter and implement it before making a GET request.
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);
This way you can get the object response using resttemplate and set contentType using MediaType.APPLICATION_JSON
public List<Employee> getListofEmployee()
{
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<List<Employee>> response = restTemplate.exchange("http://hello-server/rest/employees",
HttpMethod.GET,entity, new ParameterizedTypeReference<List<Employee>>() {});
return response.getBody(); //this returns List of Employee
}
Please add the shared dependency having jackson databind package . Hope this will clear the issue.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
In my case it was caused by the absence of the jackson-core, jackson-annotations and jackson-databind jars from the runtime classpath.
It did not complain with the usual ClassNothFoundException as one would expect but rather with the error mentioned in the original question.
Spring sets the default content-type to octet-stream when the response is missing that field. All you need to do is to add a message converter to fix this.
Other possible solution : I tried to map the result of a restTemplate.getForObject with a private class instance (defined inside of my working class). It did not work, but if I define the object to public, inside its own file, it worked correctly.
I was trying to use Feign, while I encounter same issue, As I understood HTTP message converter will help but wanted to understand how to achieve this.
#FeignClient(name = "mobilesearch", url = "${mobile.search.uri}" ,
fallbackFactory = MobileSearchFallbackFactory.class,
configuration = MobileSearchFeignConfig.class)
public interface MobileSearchClient {
#RequestMapping(method = RequestMethod.GET)
List<MobileSearchResponse> getPhones();
}
You have to use Customer Configuration for the decoder, MobileSearchFeignConfig,
public class MobileSearchFeignConfig {
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Bean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
}
public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new MappingJackson2HttpMessageConverter());
return new ObjectFactory<HttpMessageConverters>() {
#Override
public HttpMessageConverters getObject() throws BeansException {
return httpMessageConverters;
}
};
}
public class MappingJackson2HttpMessageConverter extends org.springframework.http.converter.json.MappingJackson2HttpMessageConverter {
MappingJackson2HttpMessageConverter() {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
setSupportedMediaTypes(mediaTypes);
}
}
}
In my case i was missing the No Args contructor
#Data
#AllArgsConstructor
#NoArgsConstructor
for those who are no using Lombok do add no args constructor in the mapping pojo
public ClassA() {
super();
// TODO Auto-generated constructor stub
}
also dont forget to add the Bean of Restemplate in main file if you are using the same
Infuriating problem right?
You just wanna get the result of the call, and you have a deSerialization error...that you have no clue where to look for.
Well, all is not lost.
If you change the type of call to String, you can get the JSON equivalent and then write a test to see why it is not serializing:
RestTemplate restTemplate = new RestTemplate();
String messageListString = restTemplate.getForObject("http://592693f43c87815f9b8145e9:f099c85d84d4e325a2186c02bd0caeef#backend.tdk.com/api/devicetypes/591570373c87894b4eece34d/messages", String.class);
Here is an example with an input param I used in my Kotlin project:
fun givenCUT_whenFetchingBillableItemsForAPastMonthWithoutBillingData_thenWeSucceedInGettingAnEmptyXmlResponse() {
val restTemplate = RestTemplate()
val uri = "http://localhost:$port/api/test/billing/xml/month/{month}/"
val params: MutableMap<String, String> = HashMap()
params["month"] = "2022-09-01"
val stringResponse = restTemplate.getForObject(uri, String::class.java, params)
assertNotNull(stringResponse)
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" +
"<bi:billableItems xmlns:bi=\"urn:blahblahblah\"/>\n", stringResponse)
}
If you step through that test, and harvest the actual JSON of your endpoint, you can then use a test like this, to pump it in, and see why Jackson or Gson is complains:
#Test
fun givenCUT_whenDeSerializingBEStateCorrectionsResponse_thenWeGetAnInstanceOfAListOfBillingOrdersSuccessfully() {
//Raw JSON harvested from BillingOrderControllerTest
val harvestedFEJsonBillingOrderList = "YOUR JSON Harvested from above goes here"
val mapper = ObjectMapper()
mapper.registerModule(JavaTimeModule())
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
val deSerBillingOrderList = mapper.readValue(harvestedFEJsonBillingOrderList, Array<BillingOrder>::class.java)
assertNotNull(deSerBillingOrderList)
assertEquals(1, deSerBillingOrderList.size)
}
The post is just as easy...the following is a snippet but you can see I finally gave up and commented out the serialization error part, and reverted to the String version and did the necessary with the test above in Jackson; I just this minute did that, and found 4 issues in the de-serialized JSON that Jackson explicitly reported on, and that I will fix. Then, I will revert the below to the typed version and it should have solved the problem:
try {
val result = restTemplate!!.postForEntity(uri, billingOrders, String::class.java)
/* val result = restTemplate!!.postForObject(
baseUrl,
billingOrders,
ResponseEntity<List<BillingOrder>>::class.java)*/
assertNotNull(result)
} catch (e: Exception) {
log.error("Failed restTemplate.postForObject with $e")
}
I have the following Scala enum:
object RunMode extends Enumeration {
val CLIENT_MODE = Value("CLIENT")
val SERVER_MODE = Value("SERVER")
}
I have some JSON that my app takes in as input for example:
{
"version" : "0.1",
"runMode" : "CLIENT"
}
Here the JSON field "runMode" is really my RunMode enum, and its values will always be either "CLIENT" or "SERVER". I am trying to use GSON to deserialize this JSON into an AppConfig instance:
class AppConfig(version : String, runMode : RunMode) {
def version() : String = { this.version }
def runMode() : RunMode.Value = { this.runMode }
}
I have the following GSON code:
val gson = new Gson()
val text = Source.fromFile(jsonConfigFile).mkString
gson.fromJson(text, classOf[AppConfig])
When this runs:
java.lang.RuntimeException: Unable to invoke no-args constructor for class scala.Enumeration$Value. Register an InstanceCreator with Gson for this type may fix this problem.
> Buildiat com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
at com.google.gson.Gson.fromJson(Gson.java:887)
<rest of stacktrace omitted for brevity>
So clearly, GSON expects RunMode to have a no-arg constructor, and it doesn't, and so its unable to deserialize my JSON file at runtime.
I've tried a million different combos but can't quite seem to find the magical constructor definition. So I ask: How can I add a no-arg constructor to RunMode so that GSON can deserialize it into an AppConfig instance?
This doesn't directly answer why using Gson fails, but offers an alternative. Here is an example using argonaut:
RunMode enum definition:
object RunMode extends Enumeration {
type RunMode = Value
val CLIENT_MODE = Value("CLIENT")
val SERVER_MODE = Value("SERVER")
implicit def runModeCodec: CodecJson[RunMode.RunMode] = CodecJson({
case CLIENT_MODE => "CLIENT".asJson
case SERVER_MODE => "SERVER".asJson
}, c => c.focus.string match {
case Some("CLIENT") => DecodeResult.ok(CLIENT_MODE)
case Some("SERVER") => DecodeResult.ok(SERVER_MODE)
case _ => DecodeResult.fail("Could not decode RunMode", c.history)
})
}
Definition of Foo (matching the object you want to create):
case class Foo(version: String, runMode: RunMode)
object Foo {
implicit def codec: CodecJson[Foo] =
casecodec2(Foo.apply, Foo.unapply)("version", "runMode")
}
And now the decoding/encoding example:
object ArgonautEnumCodec {
def main(args: Array[String]): Unit = {
val res: String = Foo("0.1", RunMode.CLIENT_MODE).asJson.toString
println(res)
val foo: Foo = res.decodeOption[Foo].get
println(foo)
}
}
Yields:
{"version":"0.1","runMode":"CLIENT"}
Foo(0.1,CLIENT)
Since I'm not a Scala guy, but have some Gson background, peeking some insights into how Scala works was fun to me. The reason of why you're getting the exception is that Gson cannot instantiate an abstract class scala.Enumeration.Value. The AutoConfig class content is pretty much like the following class in vanilla Java:
final class AppConfig {
final String version;
// This is where ig gets failed
final scala.Enumeration.Value runMode;
AppConfig(final String version, final scala.Enumeration.Value runMode) {
this.version = version;
this.runMode = runMode;
}
}
As far as I understand how Scala enumerations are implemented, unlike Java enumerations, they do not have their type per se, and every Scala enumeration value seem to be an instance of scala.Enumeration$Val giving not enough "host" enumeration type information from its type (however instances seem to have their outer class references). That's why custom implementing custom type adapter is not that simple and requires some inspection on the real enum type (not sure how it can be implemented, though).
Gson provides a special annotation #JsonAdapter that can annotate a certain field including a type adapter to be applied. So the AppConfig.runMode from the class above can be annotated like:
#JsonAdapter(RunModeEnumTypeAdapter.class)
final scala.Enumeration.Value runMode;
Note that it has some hint on the target type in its name. This is because there's probably no other way to specify the target enumeration type. Now, how a generic scala.Enumeration type adapter can be implemented.
// E - a special generic type bound to associate a Scala enumeration with
// So any Scala enumeration can be processed with this type adapter
abstract class AbstractScalaEnumTypeAdapter<E extends scala.Enumeration>
extends TypeAdapter<scala.Enumeration.Value> {
private final E enumeration;
protected AbstractScalaEnumTypeAdapter(final E enumeration) {
this.enumeration = enumeration;
}
#Override
#SuppressWarnings("resource")
public final void write(final JsonWriter out, final scala.Enumeration.Value value)
throws IOException {
// If the given value is null, null must be written to the writer (however it depends on a particular Gson instance configuration)
if ( value == null ) {
out.nullValue();
} else {
// Does Scala provide something like java.lang.Enumeration#name?
out.value(value.toString());
}
}
#Override
public final scala.Enumeration.Value read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case NULL:
// Consume the `null` JSON token
in.nextNull();
return null;
case STRING:
// Consume a JSON string value and lookup an appropriate Scala enumeration value by its name
final String rawValue = in.nextString();
return enumeration.withName(rawValue);
// These case labels are matter of style and cover the rest of possible Gson JSON tokens, and are not really necessary
case BEGIN_ARRAY:
case END_ARRAY:
case BEGIN_OBJECT:
case END_OBJECT:
case NAME:
case NUMBER:
case BOOLEAN:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
// Something else? Must never happen
default:
throw new AssertionError(token);
}
}
}
Now, RunMode can be bound to the type adapter above:
final class RunModeEnumTypeAdapter
extends AbstractScalaEnumTypeAdapter<RunMode$> {
// Gson can instantiate this itself
private RunModeEnumTypeAdapter() {
// This is how it looks like from the Java perspective
// And this is the "hint" I was talking about above
super(RunMode$.MODULE$);
}
}
Example of use:
final Gson gson = new Gson();
final AppConfig appConfig = gson.fromJson("{\"version\":\"0.1\",\"runMode\":\"CLIENT\"}", AppConfig.class);
System.out.println(appConfig.version);
System.out.println(appConfig.runMode);
System.out.println(gson.toJson(appConfig));
Output:
0.1
CLIENT
{"version":"0.1","runMode":"CLIENT"}
Probably not that nice and compact as Scala can do, but I hope the code above can be translated to Scala with no issues.
I have the following JSON in a file
[
{"numberEnrolledPerMonthPerWeek":
{
{"year":"2011","numberEnrolled":0,"weeks":2},
{"year":"2011","numberEnrolled":0,"weeks":3},
{"year":"2011","numberEnrolled":0,"weeks":4},
{"year":"2011","numberEnrolled":0,"weeks":5},
{"year":"2011","numberEnrolled":0,"weeks":6},
{"year":"2011","numberEnrolled":0,"weeks":7},
{"year":"2011","numberEnrolled":0,"weeks":8},
{"year":"2011","numberEnrolled":0,"weeks":9}
}
,"country":"Argentina"
},
]
When I use Jackson to deserialise this into a Java object I get the following error
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token
i am using the following code
ObjectMapper mapper = new ObjectMapper();
List<EnrolledEnrolment> enrolments = mapper.readValue(new File("src/main/resources/data/jsonQueriesTestData1.txt"),
new TypeReference<List<EnrolledEnrolment>>(){});
I have used typeReference for the initial array but do how do I use type Reference for the hashmap inside the object EnrolledEnrolment.
private Map<Integer, Enrolled> numberEnrolledPerMonthPerWeek = new HashMap<Integer,Enrolled>();
The error is thrown when it tries to parse the 2nd Array? Any ideas
Thanks
Hard to tell from the lack of an actual error in the original post, but I encountered an issue when trying to automatically deserialize JSON to an Object where the JSON contain a Map within a List.
I was seeing this error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `java.util.LinkedHashMap` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value
I fixed it be adding #JsonDeserialize(converter = JsonToMapConverter.class) to my Map in my POJO.
Where that class looked something like this
public class JsonToMapConverter extends StdConverter<String, Map<String, MyPojo>> {
#Override
public Map<String, MyPojo> convert(String value) {
try {
return JsonUtil.convertJsonToMap(value, String.class, MyPojo.class);
}
catch (Exception e) {
throw new RuntimeException(String.format("Failed to convert String to POJO [%s]", value));
}
}
}
With the utility method looking like
public static <K,V> Map<K,V> convertJsonToMap(String json, Class<K> key, Class<V> value) throws IOException {
JavaType type = mapper.getTypeFactory()
.constructMapLikeType(Map.class, key, value);
return mapper.readValue(json.getBytes(StandardCharsets.UTF_8), type);
}
I'm facing the following situation parsing a JSON.
The JSON I want to unmarshal contains an array of numbers (doubles) like this:
"position":[52.50325,13.39062]
So there is no name/value pairs.
The Problem is that I can't get the value of this array. In the Java object modeling the JSON I defined the position attribute as list of Doubles: List<Double> but after the unmarshel, the position attribute is always null.
For testing purpose I changed the content of the JSON like that:
position: [„52.50325“ ,“ 13.39062“ ]
and then there is no issue, I get the list with two elements. (Btw, this happens regardless if the position is defined as list of Strings or list of Doubles (List<String> or List<Double>))
So a workaround could be to alter the JSON response and mark this numbers as string before unmarshaling it, but I would like to avoid that, and I’m wondering if there is solution to get the value of a number array?
Here is a snapshot from the code:
ResultsListDO.java:
#XmlElement(required = true)
protected List<Double> position;
public List<Double> getPosition()
{
if (position == null) {
position = new ArrayList<Double>();
}
return this.position;
}
JSON unmarshal:
context = new JSONJAXBContext(JSONConfiguration.mapped().build(), ResultsListDO.class);
JSONUnmarshaller um = context.createJSONUnmarshaller();
ResultsListDO resultsList = um.unmarshalFromJSON(source.getReader(), ResultsListDO.class);
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Unless you have annotated your class with #XmlAccessorType(XmlAccessType.FIELD) the problem is probably that your annotation is on the field rather than the get method (see: http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html).
import java.util.*;
import javax.xml.bind.annotation.XmlElement;
public class Foo {
protected List<Double> position;
#XmlElement(required = true)
public List<Double> getPosition()
{
if (position == null) {
position = new ArrayList<Double>();
}
return this.position;
}
}
Demo Code
Below I'll demonstrate that everything works using MOXy as the JSON-binding provider.
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String,Object>();
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum18355753/input.json");
Foo foo = unmarshaller.unmarshal(json, Foo.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
input.json/Output
{
"position" : [ 52.50325, 13.39062 ]
}
Problem:
We are facing strange problems when marshalling JSONs objects including the following content {"#type":"xs:string"}. Marshalling of this object results in a NullPointerException. See the stack trace below:
java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.startElement(SAX2DOM.java:204)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.closeStartTag(ToXMLSAXHandler.java:208)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.characters(ToXMLSAXHandler.java:528)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerHandlerImpl.characters(TransformerHandlerImpl.java:172)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.DomLoader.text(DomLoader.java:128)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.text(UnmarshallingContext.java:499)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.text(InterningXmlVisitor.java:78)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.processText(StAXStreamConnector.java:324)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.handleEndElement(StAXStreamConnector.java:202)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.bridge(StAXStreamConnector.java:171)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:355)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:334)
at com.sun.jersey.json.impl.BaseJSONUnmarshaller.unmarshalJAXBElementFromJSON(BaseJSONUnmarshaller.java:108)
at com.sun.jersey.json.impl.BaseJSONUnmarshaller.unmarshalFromJSON(BaseJSONUnmarshaller.java:97)
at JerseyNPETest.testNPEUnmarshal(JerseyNPETest.java:20)
The problem occurs while getting the response from the external service and casting it implicity by glassfish (Simple REST call).
We investigated the problem and found that it is actually related to the JSON unmarshaller.
Testcase:
Marshalling -
To verify our finding, we created a class which contains a member of type Object named propertyA. Then we set the value of propertyA to "some value" and marshalled it using the default marshaller which results in the JSON string "{"#type":"xs:string","$":"some value"}".
Unmarshalling - Afterwards we used the default unmarsahller. The attempt to unmarshall this JSON string resulted in the mentioned exception.
See the test case below:
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.json.impl.BaseJSONUnmarshaller;
import org.junit.Test;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringReader;
public class JerseyNPETest {
private static final String ERROR = "{\"additionalObject\":{\"#type\":\"xs:string\",\"$\":\"some value\"}}";
#Test
public void testNPEUnmarshal() throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(AnObject.class);
final JSONConfiguration jsonConfig = JSONConfiguration.DEFAULT;
final BaseJSONUnmarshaller unmarshaller = new BaseJSONUnmarshaller(context, jsonConfig);
final StringReader reader = new StringReader(ERROR);
final AnObject result = unmarshaller.unmarshalFromJSON(reader, AnObject.class);
}
#XmlRootElement
public static class AnObject {
private Object additionalObject;
public Object getAdditionalObject() {
return additionalObject;
}
public void setAdditionalObject(final Object additionalObject) {
this.additionalObject = additionalObject;
}
}
}
Question:
How could this be solved in general e.g. by some configuration of glassfish to avoid this issue in the first place?
Currently we are working with glassfish 3.1.2.2. Any help is much appreciated!