Customize serialization of a list of JAXB objects to JSON? - 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.

Related

Spring WebFlux - Add a wrapping class before serialization

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.

Storing Document with generic JsonNode in ElasticSearch using Spring Data

I want to store unspecified json payload data in an Elasticsearch index using Spring Data with the following entity
#Document(indexName = "message")
public class Message {
#Id
private String id;
private JsonNode payload;
//getters and setters here
}
The payload varies and needs to be stored in a generic way that can also be easily loaded again that's why I'd like to use the JsonNode here.
A document with "id" gets written but the field "payload" is empty.
When I look up the document written to the index in Kibana it looks like this:
_class:
com.tryout.Message
payload:
id:
30243006-0844-4438-a7f0-db93518b340f
_id:
30243006-0844-4438-a7f0-db93518b340f
_type:
_doc
_index:
message
_score:
0
In the index mapping "payload" also wasn't created and it looks like this:
{
"mappings": {
"_doc": {
"properties": {
"_class": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
Any ideas how I can get my generic payload stored?
(I'm using Elastic v7.9.2 & Spring Booot + spring-boot-data-jpa v2.3.5 and spring-data-elasticsearch v4.1.1)
I had this problem after upgrading spring-data-elastic-search version to 4, and unfortunately I didn't find a clear answer for it. Reading spring data elasticsearch documentation,I found out this:
As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the MappingElasticsearchConverter is used.
After searching several days, finally I solve my problem this way, I hope it could help others.
p.s : JsonNode can include jsonArray or JsonObject, so these two data-types should be handled while reading/writing.
class JsonElasticSearchConverter extends MappingElasticsearchConverter {
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
CustomElasticSearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
super(mappingContext);
setConversions(conversions);
}
CustomElasticSearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, GenericConversionService conversionService) {
super(mappingContext, conversionService);
setConversions(conversions);
}
#Override
protected <R> R readValue(#Nullable Object source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
if (targetType.getType() == JsonNode.class) {
return (R) mapStoredValueToJsonNode(source);
}
return super.readValue(source, property, targetType);
}
#Override
protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation<?> typeHint, Object value) {
if (typeHint.getType() == JsonNode.class) {
return getWriteJsonNodeValue(value);
}
return super.getWriteComplexValue(property, typeHint, value);
}
private JsonNode mapStoredValueToJsonNode(Object source) {
JsonNode jsonNode = null;
ObjectMapper mapper = new ObjectMapper();
if (source instanceof ArrayList) {
ArrayNode array = mapper.valueToTree(source);
jsonNode = mapper.valueToTree(array);
}
if (source instanceof HashMap) {
jsonNode = mapper.convertValue(source, JsonNode.class);
}
return jsonNode;
}
private Object getWriteJsonNodeValue(Object value) {
JsonNode jsonNode = null;
ObjectMapper mapper = new ObjectMapper();
if (value instanceof ObjectNode)
try {
jsonNode = mapper.readTree(value.toString());
} catch (IOException shouldNotHappened) {
//log.warn("This error should not happened" + shouldNotHappened.getMessage());
}
else if (value instanceof ArrayNode) {
ArrayNode array = mapper.valueToTree(value);
jsonNode = mapper.valueToTree(array);
}
return jsonNode;
}
}

JSON Deserialization with property default value not working

I am using Newtonsoft to deserialize data from a file. When I deserialize two different instances from two different sets of data, both instances' property ends up having the same value. I have created a small project to repro the issue. Here are my 2 JSON files
File1.json:
{
"Name": "File1",
"SomeProperty":
{
"Value": 1
}
}
File2.json:
{
"Name": "File2",
"SomeProperty":
{
"Value": 2
}
}
SomeProperty.cs
namespace Json
{
public class SomePropertyDto
{
public static SomePropertyDto Default = new SomePropertyDto
{
Value = 0
};
public int Value { get; set; }
}
}
FileDataDto.cs
namespace Json
{
public class FileDataDto
{
public string Name { get; set; }
public SomePropertyDto SomeProperty
{
get => someProperty;
set => someProperty = value;
}
private SomePropertyDto someProperty = SomePropertyDto.Default;
}
}
Program.cs
using System.IO;
using Newtonsoft.Json;
namespace Json
{
class Program
{
static void Main(string[] args)
{
string json1 = File.ReadAllText("File1.json");
string json2 = File.ReadAllText("File2.json");
FileDataDto fileData1 = JsonConvert.DeserializeObject<FileDataDto>(json1);
FileDataDto fileData2 = JsonConvert.DeserializeObject<FileDataDto>(json2);
}
}
}
After deserializing both instances of FileDataDto, both their SomeProperty values are the same. However if I do not initialise the FileDataDto someProperty field to SomePropertyDto.Default,
private SomePropertyDto someProperty;// = SomePropertyDto.Default;
it works correctly. If I include the initialisation to the default value
private SomePropertyDto someProperty = SomePropertyDto.Default;
after deserializing fileData1, the SomeProperty value equals 1 as expected. However, after deserializing fileData2, both fileData1 and FileData2 instances' SomeProperty value equals 2 which is not what is expected.
According to https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonSerializerSettings.cs#L46, the default object creation setting is "Auto", which means https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/ObjectCreationHandling.cs#L34
Reuse existing objects, create new objects when needed.
So when your Default object is there, someProperty stay this, the same, shared object for all FileDataDto instances.
Provide customized JsonSerializerSettings (with ObjectCreationHandling set to Replace) if you need that Default value.

Moshi Custom JsonAdapter

I am trying to create a custom JsonAdapter for my JSON data that would bypass the serialization of specific field. Following is my sample JSON:
{
"playlistid": 1,
"playlistrows": [
{
"rowid": 1,
"data": {
"123": "title",
"124": "audio_link"
}
}
]
}
The JSON field data in above have dynamic key numbers, so I want to bypass this data field value and return JSONObject.
I am using RxAndroid, Retrofit2 with Observables. I have created a service class:
public static <S> S createPlaylistService(Class<S> serviceClass) {
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(baseURL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClientBuilder.build())
.addConverterFactory(MoshiConverterFactory.create());
return builder.build().create(serviceClass);
}
I am calling this service using observable like this:
#GET("http://www.mylink.com/wp-json/subgroup/{subgroupId}/playlist/{comboItemId}")
Observable<Playlist> getPlaylist(#Path("subgroupId") int subgroupId, #Path("comboItemId") int comboItemId);
Then I run it like this:
ServiceBuilder.createPlaylistService(FHService.class).getPlaylist(123, 33);
My Pojo classes look like this:
public class Playlist {
#Json(name = "playlistid")
public Long playlistid;
#Json(name = "playlistrows")
public List<Playlistrow> playlistrows = null;
}
public class Playlistrow {
#Json(name = "rowid")
public Long rowid;
#Json(name = "data")
public Object data;
}
The problem is it would return a data value in this format:
{
123=title,
124=audio_link
}
which is invalid to parse as JSONObject.
I have Googled a lot and have also checked some Moshi example recipes but I had got no idea about how to bypass this specific field and return valid JSONObject, since I am new to this Moshi library.

type handling by Jackson/Spring conversion of JSON to java POJO

I'm using
Spring 3.1.0.RELEASE
Jackson 1.9.5
I'm using org.springframework.web.client.RestTemplate's getForObject() method:
getForObject(String url, Class<?> responseType, Map<String, ?> urlVariables) throws RestClientException
Here's my JSON:
{
"someObject": {
"someKey": 42,
},
"key2": "valueA"
}
Here's the POJO used to hold it:
SomeClass.java:
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
#Generated("org.jsonschema2pojo")
#JsonPropertyOrder({
"someObject",
"key2"
})
public class SomeClass {
#JsonProperty("someObject")
private SomeObject someObject;
#JsonProperty("key2")
private String key2;
#JsonProperty("someObject")
public LocationInfo getSomeObject() {
return someObject;
}
#JsonProperty("someObject")
public void setLocationInfo(SomeObject someObject) {
this.someObject = someObject;
}
}
SomeObject.java:
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
#Generated("org.jsonschema2pojo")
#JsonPropertyOrder({
"someKey"
})
public class SomeObject{
#JsonProperty("someKey")
private String someKey;
#JsonProperty("someKey")
public String getSomeKey() {
if(someKey==null){
someKey = "";
}
return someKey.toUpperCase();
}
#JsonProperty("someKey")
public void setSomeKey(String someKey) {
this.someKey = someKey;
}
}
It works. Given the JSON structure, I get a String value of "42" in the property someKey of class SomeObject
I don't understand why. Is there some magical conversion going on behind the scenes of which I'm unaware?
Can the conversion be counted on? Also, i'm not currently getting any whitespace at the beginning or end of the String someKey. Is that something I can count on as well, since the integer value cannot have any whitespace?
Check out the code at https://github.com/joelittlejohn/jsonschema2pojo if you want to really understand how it works.
Yes the conversion can be counted on, yes you can count on their not being whitespace in the String in the pojo.
In a nutshell the fields from the JSON file are read in, then these get mapped to the member variables/setter methods of the Pojos that is passed in as your responseType.