Spring WebFlux - Add a wrapping class before serialization - json

I'm developing APIs for an exam project, but I wanted their responses to be consistently using a wrapping class on all of them (Telegram Bot API style for those who know them).
So, for example, having these two classes:
public class User {
public int id;
public String name;
}
public class Item {
public int id;
public String itemName;
public User owner;
}
What Spring returns to me is this output:
{
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
What I want instead is for this output to be returned:
{
"ok": true,
"data": {
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
}
Maybe using a class wrapper like this:
public class ResponseWrapper<T> {
public boolean ok;
public T data;
}
Is it possible to do this?

I understand you need a global setting to convert all your responses into a standard one. For this you can implement ResponseBodyAdvice and have a common structure for all your api responses. Refer this link for a detailed example
Edit: For spring-webflux you can extend ResponseBodyResultHandler and override handleResult. An example is given in this answer

I thank #JustinMathew for the help, at the end, in my case (using Spring WebFlux with Kotlin), the ResponseBodyResultHandler class was more useful to me.
// File: /MicroserviceApplication.kt
#SpringBootApplication
class MicroserviceApplication {
#Autowired
lateinit var serverCodecConfigurer: ServerCodecConfigurer
#Autowired
lateinit var requestedContentTypeResolver: RequestedContentTypeResolver
#Bean
fun responseWrapper(): ResponseWrapper = ResponseWrapper(
serverCodecConfigurer.writers, requestedContentTypeResolver
)
}
// File: /wrapper/model/Response.kt
data class Response<T>(
val ok: Boolean,
val data: T?,
val error: Error? = null
) {
data class Error(
val value: HttpStatus,
val message: String?
)
}
// File: /wrapper/ResponseWrapper.kt
class ResponseWrapper(writers: List<HttpMessageWriter<*>>, resolver: RequestedContentTypeResolver) :
ResponseBodyResultHandler(writers, resolver) {
override fun supports(result: HandlerResult): Boolean =
(result.returnType.resolve() == Mono::class.java)
|| (result.returnType.resolve() == Flux::class.java)
#Throws(ClassCastException::class)
override fun handleResult(exchange: ServerWebExchange, result: HandlerResult): Mono<Void> {
val body = when (val value = result.returnValue) {
is Mono<*> -> value
is Flux<*> -> value.collectList()
else -> throw ClassCastException("The \"body\" should be Mono<*> or Flux<*>!")
}
.map { r -> Response(true, r, null) }
.onErrorMap { e ->
if (e !is Response.Error)
Response.Error(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error")
else e
}
.onErrorResume { e -> Mono.just(Response(false, null, e as Response.Error)) }
return writeBody(body, returnType, exchange)
}
companion object {
#JvmStatic
private fun methodForReturnType(): Mono<Response<Any>>? = null
private val returnType: MethodParameter = MethodParameter(
ResponseWrapper::class.java.getDeclaredMethod("methodForReturnType"), -1
)
}
Edit: I made of this answer a library for Spring WebFlux 2.7.3 here.
P.S. I also took a cue from this other question, which faces the same problem but with Java.

Related

Android Kotlin parsing nested JSON

I'm still pretty new to programming with Kotlin but I can't seem to figure out the correct way to parse my JSON. I'm attempting to get "title" and "body" from "notification" in "unackd" array only.
So far I've got:
private fun parse(): Boolean {
try {
val ja = JSONArray(jsonData)
var jo: JSONObject
users.clear()
var user: User
for (i in 0 until ja.length()) {
jo = ja.getJSONObject(i)
val name = jo.getString("title")
val username = jo.getString("body")
user = User(username,name)
users.add(user)
}
return true
} catch (e: JSONException) {
e.printStackTrace()
return false
}
}
Meanwhile my JSON is structured as so:
{
"unackd": [
{
"notification": {
"title": "Title Test Number 200",
"body": "passage local they water difficulty tank industry allow increase itself captured strike immediately type phrase driver change save potatoes stems addition behavior grain trap rapidly love refused way television bright 1100"
},
"data": {
"id": "1100",
"phone": "+15555551234"
}
},
{
"notification": {
"title": "Title Test Number 199",
"body": "announced beside well noted mysterious farm he essential likely deeply vast touch 1099"
},
"data": {
"id": "1099",
"phone": "+15555551234"
}
}
],
"ackd": [
{
"notification": {
"title": "Title Test Number 200",
"body": "passage local they water difficulty tank industry allow increase itself captured strike immediately type phrase driver change save potatoes stems addition behavior grain trap rapidly love refused way television bright 1100"
},
"data": {
"id": "1100",
"phone": "+15555551234"
}
},
{
"notification": {
"title": "Title Test Number 199",
"body": "announced beside well noted mysterious farm he essential likely deeply vast touch 1099"
},
"data": {
"id": "1099",
"phone": "+15555551234"
}
}
]
}
I believe my issue is getting into "notification" to then get the strings "title" and "body". Which I've tried
test1 = jo.getJSONObject("notification")
Any help would be appreciated!
EDIT:
This is my logcat error, I assume it has to do with the JSON.typeMismatch:
at org.json.JSON.typeMismatch(JSON.java:111)
at org.json.JSONArray.<init>(JSONArray.java:96)
at org.json.JSONArray.<init>(JSONArray.java:108)
at android.os.AsyncTask$2.call(AsyncTask.java:333)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
The exception message suggests that you're passing data that doesn't represent a JSON array when instantiating JSONArray:
at org.json.JSON.typeMismatch(JSON.java:111)
at org.json.JSONArray.<init>(JSONArray.java:96)
The JSON you've attached is in fact a JSON object, notice that its content is enclosed in {}. Hence to access the "unackd" array, you need to first create a JSON object, and then reference the array inside of it:
val root = JSONObject(jsonData)
val ja = root.getJSONArray("unackd")
// the rest of your code goes here
Listen friend , parsing the JSON Object with JSON ARRAY with key (like: unackd , ackd) is so simple.
There are 2 ways:
1st Way)
Parse your JSON to Pojo schema
http://www.jsonschema2pojo.org/
public class Ackd {
#SerializedName("notification")
#Expose
private Notification_ notification;
#SerializedName("data")
#Expose
private Data_ data;
public Notification_ getNotification() {
return notification;
}
public void setNotification(Notification_ notification) {
this.notification = notification;
}
public Data_ getData() {
return data;
}
public void setData(Data_ data) {
this.data = data;
}
}
public class Data {
#SerializedName("id")
#Expose
private String id;
#SerializedName("phone")
#Expose
private String phone;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
No need to Make all class for parsing (like ackd (Json Array))
2nd Way)
You need to PARSE JSON array with name only unackd not ackd.
String jsonStr = sh.makeServiceCall(url);
JSONObject jsonObj = new JSONObject(jsonStr);
// Getting JSON Array node
JSONArray unA= jsonObj.getJSONArray("unackd");
for (int i = 0; i < unA.length(); i++)
{
JSONObject c = unA.getJSONObject(i);
String title= c.getString("title");
String body= c.getString("body");
}
Auto generate Data class
http://www.jsonschema2pojo.org/
I suppose that your class is named Response.java
Response object=new Gson().fromjson(jsonContentFile,Response.class);
Following data classes are generated for your JSON using https://json2kotlin.com
data class Json4Kotlin_Base (
val unackd : List<Unackd>,
val ackd : List<Ackd>
)
and
data class Data (
val id : Int,
val phone : Int
)
and
data class Notification (
val title : String,
val body : String
)
and
data class Ackd (
val notification : Notification,
val data : Data
)
and
data class Unackd (
val notification : Notification,
val data : Data
)
Here's a video that explains how to implement these when generated.

GraphQL java send custom error in json format

I am working in an graphql application where I have to send custom error object / message in json irrespective of whether it occurs in servlet or service.
Expected error response
{ errorCode: 400 //error goes here,
errorMessage: "my error mesage"}
It will be helpful if someone could guide me to achieve the above requirement.
GraphQL specification defines a clear format for the error entry in the response.
According to the spec, it should like this (assuming JSON format is used):
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {/* You can place data in any format here */}
}
]
So you won't find a GraphQL implementation that allows you to extend it and return some like this in the GraphQL execution result, for example:
"errors": [
{
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
]
However, the spec lets you add data in whatever format in the extension entry. So you could create a custom Exception on the server side and end up with a response that looks like this in JSON:
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
}
]
It's quite easy to implement this on GraphQL Java, as described in the docs. You can create a custom exception that overrides the getExtensions method and create a map inside the implementation that will then be used to build the content of extensions:
public class CustomException extends RuntimeException implements GraphQLError {
private final int errorCode;
public CustomException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
}
#Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new LinkedHashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
#Override
public List<SourceLocation> getLocations() {
return null;
}
#Override
public ErrorType getErrorType() {
return null;
}
}
then you can throw the exception passing in the code and message from inside your data fetchers:
throw new CustomException(400, "A custom error message");
Now, there is another way to tackle this.
Assuming you are working on a Web application, you can return errors (and data, for that matter) in whatever format that you want. Although that is a bit awkward in my opinion. GraphQL clients, like Apollo, adhere to the spec, so why would you want to return a response on any other format? But anyway, there are lots of different requirements out there.
Once you get a hold of an ExecutionResult, you can create a map or object in whatever format you want, serialise that as JSON and return this over HTTP.
Map<String, Object> result = new HashMap<>();
result.put("data", executionResult.getData());
List<Map<String, Object>> errors = executionResult.getErrors()
.stream()
.map(error -> {
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("errorMessage", error.getMessage());
errorMap.put("errorCode", 404); // get the code somehow from the error object
return errorMap;
})
.collect(toList());
result.put("errors", errors);
// Serialize "result" and return that.
But again, having a response that doesn't comply with the spec doesn't make sense in most of the cases.
The other posted answer didn't work for me.
I found a solution by creating the following classes:
1) A throwable CustomException of GraphQLError type (just like mentioned in another answer).
2) Creating a GraphQLError Adaptor, which is not a Throwable.
3) A custom GraphQLErrorHandler to filter the custom exception.
Step 1:
The below throwable CustomGraphQLException implements GraphQLError because the GraphQLErrorHandler interface accepts errors only of type GraphQLError.
public class CustomGraphQLException extends RuntimeException implements GraphQLError {
private final int errorCode;
private final String errorMessage;
public CustomGraphQLException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
#Override
public List<SourceLocation> getLocations() {
return null;
}
#Override
public ErrorType getErrorType() {
return null;
}
#Override
public String getMessage() {
return this.errorMessage;
}
#Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new HashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
}
Step 2:
A non-throwable adaptor of GraphQLError is created to avoid the stack-trace of the above custom exception being passed in the final GraphQL Error Response.
public class GraphQLErrorAdaptor implements GraphQLError {
private final GraphQLError graphQLError;
public GraphQLErrorAdaptor(GraphQLError graphQLError) {
this.graphQLError = graphQLError;
}
#Override
public List<SourceLocation> getLocations() {
return graphQLError.getLocations();
}
#Override
public ErrorType getErrorType() {
return graphQLError.getErrorType();
}
#Override
public String getMessage() {
return graphQLError.getMessage();
}
#Override
public Map<String, Object> getExtensions() {
return graphQLError.getExtensions();
}
}
Step 3:
A custom GraphQLErrorHandler is implemented to filter the custom CustomGraphQLException and avoid its replacement with the default graphQL error response.
public class CustomGraphQLErrorHandler implements GraphQLErrorHandler {
public CustomGraphQLErrorHandler() { }
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
List<GraphQLError> clientErrors = this.filterGraphQLErrors(errors);
List<GraphQLError> internalErrors = errors.stream()
.filter(e -> isInternalError(e))
.map(GraphQLErrorAdaptor::new)
.collect(Collectors.toList());
if (clientErrors.size() + internalErrors.size() < errors.size()) {
clientErrors.add(new GenericGraphQLError("Internal Server Error(s) while executing query"));
errors.stream().filter((error) -> !this.isClientError(error)
).forEach((error) -> {
if (error instanceof Throwable) {
LOG.error("Error executing query!", (Throwable) error);
} else {
LOG.error("Error executing query ({}): {}", error.getClass().getSimpleName(), error.getMessage());
}
});
}
List<GraphQLError> finalErrors = new ArrayList<>();
finalErrors.addAll(clientErrors);
finalErrors.addAll(internalErrors);
return finalErrors;
}
protected List<GraphQLError> filterGraphQLErrors(List<GraphQLError> errors) {
return errors.stream().filter(this::isClientError).collect(Collectors.toList());
}
protected boolean isClientError(GraphQLError error) {
return !(error instanceof ExceptionWhileDataFetching) && !(error instanceof Throwable);
}
protected boolean isInternalError(GraphQLError error) {
return (error instanceof ExceptionWhileDataFetching) &&
(((ExceptionWhileDataFetching) error).getException() instanceof CustomGraphQLException);
}
}
Step 4:
Configure the CustomGraphQLErrorHandler in GraphQLServlet. I am assuming you are using spring-boot for this step.
#Configuration
public class GraphQLConfig {
#Bean
public ServletRegistrationBean graphQLServletRegistrationBean(
QueryResolver queryResolver,
CustomGraphQLErrorHandler customGraphQLErrorHandler) throws Exception {
GraphQLSchema schema = SchemaParser.newParser()
.schemaString(IOUtils.resourceToString("/library.graphqls", Charset.forName("UTF-8")))
.resolvers(queryResolver)
.build()
.makeExecutableSchema();
return new ServletRegistrationBean(new SimpleGraphQLServlet(schema,
new DefaultExecutionStrategyProvider(), null, null, null,
customGraphQLErrorHandler, new DefaultGraphQLContextBuilder(), null,
null), "/graphql");
}
}
Reference

Test with JUnit an request

I wanna to test an request with JUnit with an request like this but RxUtils.applySchedulersAndErrorMapper() return null. Is any possibilities to test that?
override fun onContinueClicked(phoneNumber: String) {
mView.showLoading()
mUserService.checkUserApprovedStatus(phoneNumber)
.compose(RxUtils.applySchedulersAndErrorMapper())
.subscribe({ response ->
//Success
}, { error ->
//Error
})
}
here is where I setup the presenter and mUserService for presenter
#Mock
private PhoneContract.View view;
#Mock
private UserService userService;
#Before
public void setup() {
presenter = new PhonePresenter(this.view);
presenter.mUserService = userService;
}
here is the test method
#Test
public void onContinueClicked_SendJustNumbers() {
String phoneNumber = "(01234567890)";
// when
presenter.onContinueClicked(phoneNumber);
// then
verify(view, times(1)).showLoading();
}
and here is the RXUtils class:
class RxUtils {
companion object {
#SuppressLint("CheckResult")
fun <E> applySchedulersAndErrorMapper(): ObservableTransformer<E, E> {
return ObservableTransformer { o ->
o.flatMap(Function<E, ObservableSource<E>> { element ->
val genericResponse = element as GenericResponse<*>
#Suppress("UNCHECKED_CAST")
return#Function Observable.just(genericResponse as E)
}).onErrorResumeNext(Function<Throwable, ObservableSource<E>> { t ->
if (t is ApiException) {
return#Function Observable.error(t)
}
var genericResponse: GenericResponse<*>? = null
return#Function Observable.error(ApiException(t.message ?: "", genericResponse?.result ?: Result()))
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
}
}
}
}
Here is the stacktrace where I receive null for RxUtils.applySchedulersAndErrorMapper()
java.lang.NullPointerException
If it relates to the SDK, it probably won't work in a unit test. You didn't include your imports, so it's impossible to tell at a glance, but I know from experience that you can't use this in a unit test
AndroidSchedulers.mainThread()
You need to replace that with, say, Schedulers.trampoline()
Example of how to set a custom scheduler for testing
Note, this is only an example, and there are other valid solutions.
class RxUtils {
companion object {
// add this
#VisibleForTesting var mainScheduler = AndroidSchedulers.mainThread()
#VisibleForTesting var ioScheduler = Schedulers.io()
#SuppressLint("CheckResult")
fun <E> applySchedulersAndErrorMapper(): ObservableTransformer<E, E> {
return ObservableTransformer { o ->
o.flatMap(Function<E, ObservableSource<E>> { element ->
val genericResponse = element as GenericResponse<*>
#Suppress("UNCHECKED_CAST")
return#Function Observable.just(genericResponse as E)
}).onErrorResumeNext(Function<Throwable, ObservableSource<E>> { t ->
if (t is ApiException) {
return#Function Observable.error(t)
}
var genericResponse: GenericResponse<*>? = null
return#Function Observable.error(ApiException(t.message ?: "", genericResponse?.result ?: Result()))
})
.observeOn(mainScheduler)
.subscribeOn(ioScheduler)
}
}
}
}
And in your test:
#Before fun setup() {
RxUtils.mainScheduler = Schedulers.trampoline()
RxUtils.ioScheduler = Schedulers.trampoline()
}
#After fun teardown() {
RxUtils.mainScheduler = AndroidSchedulers.mainThread()
RxUtils.ioScheduler = Schedulers.io()
}
EDIT in response to updated post with more information on test
First of all, you should post WAY MORE CODE. It's frustrating having to pull it out of you by dribs and drabs. Anyway. You have the following:
#Mock
private UserService userService;
That creates a mock UserService, sure, but it doesn't stub anything. When you call userService.anyFunctionAtAll(), it will return null by default. There's your NPE. You have to stub it. For example:
Mockito.when(userService.anyFunctionAtAll()).thenReturn(somePredeterminedValue)
Please refer to the Mockito website for more information.

De-deserializing JSON using Gson

I'm looking for help in de-serializing a JSON to an instance of its POJO. The top level POJO Graph.java has an attribute of type HashMap. While serializing it throws
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line n column nn path
$.degreesCountMap[0]
I know exactly what it means and how to fix it for for a top level collection but not sure how to specify the Type for an attribute of a another object.
I did review discussions on such issues in this and many other forums but I don't really see an answer that can help me.
I would greatly appreciate any help on this.
Here's the JSON of Graph:
{
"nodeCount":3,
"edgeCount":2,
"degreesCountMap":[
{
"ONE":2
},
{
"TWO":1
}
],
"nodes":[
{
"index":0,
"connectedIndices":[
1
]
},
{
"index":1,
"connectedIndices":[
0,
2
]
},
{
"index":2,
"connectedIndices":[
1
]
}
]
}
Here are the POJOs
Graph.java
public class Graph {
private HashMap<Degree, Integer> degreesCountMap;
private Integer edgeCount;
private Integer nodeCount;
private ArrayList<Node> nodes;
public HashMap<Degree, Integer> getDegreesCountMap() {
return degreesCountMap;
}
public void setDegreesCountMap(HashMap<Degree, Integer> degreesCountMap) {
this.degreesCountMap = degreesCountMap;
}
public void setNodes(ArrayList<Node> nodes) {
this.nodes = nodes;
}
}
Degree.java
public enum Degree {
ZERO, ONE, THREE, FOUR;
}
Node.java
public class Node {
private ArrayList<Integer> connectedIndices;
private int index;
public ArrayList<Integer> getConnectedIndices() {
return connectedIndices;
}
public int getIndex() {
return index;
}
public void setConnectedIndices(ArrayList<Integer> connectedIndices) {
this.connectedIndices = connectedIndices;
}
public void setIndex(int index) {
this.index = index;
}
}
GraphTest.java
#Test
public void testJsonToGraph() {
String json = "{\"nodeCount\":3,\"edgeCount\":2,"
+ "\"degreesCountMap\":[{\"ONE\":2},{\"TWO\":1}],"// <--to fail
+ "\"nodes\":[{\"index\":0,\"connectedIndices\":[1]},"
+ "{\"index\":1,\"connectedIndices\":[0,2]},"
+ "{\"index\":2,\"connectedIndices\":[1]}]}";
try {
graph = gson.fromJson(json, Graph.class);
assertNotNull(graph);
} catch (Exception e) { // Intentionally capturing to diagnose
e.printStackTrace();
}
}
The problem is that the JSON you posted is not valid.
Because Map can be used to map any object to any object Gson have to make map as array with two objects.
The valid JSON for map object would looks like this:
"degreesCountMap": [
[
"ONE",
2
],
[
"TWO",
1
]
]
but since you are using enum as keys the following code is also valid:
"degreesCountMap": {
"TWO": 1,
"ONE": 2
}
Solution: edit your json to valid one. Also, I think you are missing TWO in your degree enum.
Note: Because you use enum there is just "ONE" but if you used a typical object for a key it could looks like this:
"degreesCountMap": [
[
{ "degree": "ONE" },
2
],
[
{ "degree": "TWO" },
1
]
]

Customize serialization of a list of JAXB objects to JSON?

I am planning to serialize list of JAXB objects to JSON response. Currently below is the format I am getting. In the below response I am seeing one more object in between is "systemInfoList" which actually is showing the array. Instead I want the dependent_systems_infos should directly show array []. Also if there is a single system info response also still it should should show in the array format. I am using the Jackson parser, cxf.
Format currently I am getting:
{
"dependent_systems_infos":{
"systemInfoList":[
{
"system_name":"PZ_Service",
"system_type":"Internal",
"service_infos":[
{
"service_name":"getPZAttributes",
"status":"DOWN",
"response_time_ms":50
}
]
},
{
"system_name":"OMS",
"system_type":"External",
"service_infos":[
{
"service_name":"CreateOrder",
"status":"UP",
"response_time_ms":2000
},
{
"service_name":"CancelOrder",
"status":"UP",
"response_time_ms":500
}
]
}
]
}
}
Format I need:
{
dependent_system_infos : [
{
system_name : 'OMS'
system_type: 'External'
services_infos: [
{
service_name : 'CreateOrder'
status : 'UP'
response_time_ms : 2000
},
{
service_name : 'CancelOrder'
status : 'UP'
response_time_ms : 2000
}
]
},
{
system_name : 'PZ_Service'
system_type: 'Internal'
services_infos: [
{
service_name : 'getPZAttributes'
status : 'UP'
response_time_ms : 2000
}
]
}
]
}
JAXB classes I wrote:
#XmlRootElement(name = "dependent_systems_infos")
#XmlAccessorType(XmlAccessType.FIELD)
public class ItineraryStatusResponse {
private List<SystemInfo> systemInfoList;
#XmlList
public List<SystemInfo> getSystemInfoList() {
return systemInfoList;
}
public void setSystemInfoList(List<SystemInfo> systemInfoList) {
this.systemInfoList = systemInfoList;
}
}
#XmlType(propOrder = {
"systemName",
"systemType",
"serviceInfoList"
})
#XmlAccessorType(XmlAccessType.FIELD)
public class SystemInfo {
#XmlElement(name = "system_name", required = true)
protected SystemName systemName;
#XmlElement(name = "system_type", required = true)
protected SystemType systemType;
#XmlElement(name = "service_infos", required = true)
protected List<ServiceInfo> serviceInfoList;
}
It would help to know how you're generating the output, but the main issue is that you are serializing a root object that contains a list when you really only want to serialize the list itself. What would you expect the outputted list to look like if ItineraryStatusResponse had other fields in it?
You can remove the #XmlRootElement annotation and mark the list as an element named "dependent_systems_infos":
#XmlAccessorType(XmlAccessType.FIELD)
public static class ItineraryStatusResponse {
private List<SystemInfo> systemInfoList;
#XmlElement(name = "dependent_systems_infos", required = true)
public List<SystemInfo> getSystemInfoList() {
return systemInfoList;
}
public void setSystemInfoList(List<SystemInfo> systemInfoList) {
this.systemInfoList = systemInfoList;
}
}
If you are doing the serialization yourself, another approach would be to drop the ItineraryStatusResponse object entirely (since it's just a wrapper around the list), and then serialize the list itself with SerializationFeature.WRAP_ROOT_VALUE = true and a root name you provide:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
mapper.setAnnotationIntrospector(introspector);
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter().withRootName("dependent_systems_infos");
System.out.println(writer.writeValueAsString(systemInfoList));
Both of these approaches provide the desired output in my testing with Jackson 2.2.