Spring rest controller result wrapping with JsonView / MappingJacksonValue - json

I have a Spring API that allows the user to specify the JsonView of each call using a view param like so:
/api/v1/person/1?view=viewName
I then use Spring's MappingJacksonValue to set the correct view dynamically instead of using #JsonView() annotation and finally I just return the MappingJacksonValue instance which produces something along the lines of
[
{ id: 1 },
{ id: 2 }
]
I can't for the life of me figure out how to easily wrap my MappingJacksonValue instances in an ObjectNode so that I can change all API results from the snippet above to this
{
"data" : [
{ id: 1 },
{ id: 2 }
]
}
I tried using a regular HashMap<> but that didn't work - the serialization completely ignores the MappingJacksonValue view and it also produces Map-specific results
{
data: {
value: [],
serializationView: "com.blah.models.view.View$Id",
filters: null,
jsonpFunction: null
}
}
So can someone pls let me know what's the best way to achieve result wrapping in my scenario?
Thanks in advance!

If anyone comes across this post looking to do the same thing, I realized I was looking at the problem in the wrong way. I ended up creating a ServiceResponse class and wrapped up the object in there like so
public class ServiceResponse {
#JsonView(View.Id.class)
private Object data;
public ServiceResponse (Object data) {
this.data = data;
}
}
So essentially instead of returning new MappingJacksonValue(someReturnObject) I'm returning new MappingJacksonValue(new ServiceResponse(someReturnObject)). This way everything is wrapped nicely in a data JSON object and the setSerializationView method still filters my objects properly.
Hope this helps someone.

Related

Understanding Spring HATEOAS error: Embedded wrapper returned null for both the static rel and the rel target type

As mentioned, I would like to get help understanding this error which is quite cryptic to me:
WARN 6436 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Embedded wrapper org.springframework.hateoas.server.core.EmbeddedWrappers$EmbeddedCollection#7538c537 returned null for both the static rel and the rel target type! Make sure one of the two returns a non-null value!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Embedded wrapper org.springframework.hateoas.server.core.EmbeddedWrappers$EmbeddedCollection#7538c537 returned null for both the static rel and the rel target type! Make sure one of the two returns a non-null value! (through reference chain: org.springframework.hateoas.CollectionModel["_embedded"])]
This error came about after I tried accessing a collection model through the following method within my Spring Controller. What I'm trying to do is to return all the share transactions associated with a particular stock code.
#Autowired
private ShareTransactionAssembler shareTransactionAssembler;
#GetMapping("/{stockCode}/transactions")
public CollectionModel<ShareTransactionModel> getShareTransactions (#PathVariable("stockCode") String stockCodeString) {
StockCode stockCode = new StockCode(stockCodeString);
List<ShareTransaction> shareTransactionList = stockQueryService.getShareTransactions(stockCode);
return shareTransactionAssembler.toCollectionModel(shareTransactionList, stockCode);
}
The following is my shareTransactionAssembler code:
#Component
public class ShareTransactionAssembler extends RepresentationModelAssemblerSupport<ShareTransaction, ShareTransactionModel> {
public ShareTransactionAssembler() {
super(StockController.class, ShareTransactionModel.class);
}
#Override
public ShareTransactionModel toModel(ShareTransaction shareTransaction) {
String stockCodeString = shareTransaction.getStock().getStockCode().getStockCodeAsString();
Link selfLink = linkTo(
methodOn(StockController.class)
.findShareTransaction(stockCodeString, shareTransactionModel.getId())
)
.withSelfRel();
shareTransactionModel.add(selfLink);
Link stockLink = linkTo(
methodOn(StockController.class)
.findStock(stockCodeString)
)
.withRel("Stock");
shareTransactionModel.add(stockLink);
return shareTransactionModel;
}
public CollectionModel<ShareTransactionModel> toCollectionModel (List<ShareTransaction> shareTransactionList, StockCode stockCode) {
CollectionModel<ShareTransactionModel> shareTransactionModels = super.toCollectionModel(shareTransactionList);
Link selfLink = linkTo(
methodOn(StockController.class)
.getShareTransactions(stockCode.getStockCodeAsString())
)
.withSelfRel();
shareTransactionModels.add(selfLink);
return shareTransactionModels;
}
}
In case you need to know, accessing the individual share transactions seem to work fine. An example JSON response is as follows:
{
"id": 1,
"transactPrice": "13.00",
"commissionPaid": "1.05",
"transactTimeDate": "2020-03-07T13:05",
"transactUnits": 30,
"_links": {
"self": {
"href": "http://localhost:8080/stock/NVDA/transactions/1"
},
"Stock": {
"href": "http://localhost:8080/stock/NVDA"
}
}
}
I would really appreciate if you can shed some light on this! Thanks!
I stumbled onto this error as well. I got it fixed by extending my model class with RepresentationModel instead of EntityModel.
Still searching why this makes a difference..
I had this problem too and this is my understanding based on reading the documentation here (see section 2.4):
https://docs.spring.io/spring-hateoas/docs/current/reference/html/#reference
You are not supposed to directly inherit from EntityModel. EntityModel is a generic RepresentationModel implementation that can be used for representing singular objects or concepts. You can use it when you don't want to write your own RepresentationModel. It should just serialize all fields in the object you pass it.
RepresentationModel is the root class for defining your own representation models.

Add 'standard' JSON fields to serialized objects in REST API

Motivated by this: Google JSON Style Guide, I want to insert a bit of custom serialization logic to my rest API. I'm using the WebAPI 2 and JSON.NET. My goal is to wrap the 'payload' of my response in the 'data' field of the main JSON response, as described in the style guide, include an apiVersion field in every response, and that sort of thing. Of course the controller actions just return straight POCO's, and I want to modify the container that they're sent inside of, not the POCOs themselves, so:
{
"id": "111",
"apiVersion": "1.0",
"data": {
"kind": "monkey",
"name": "manny",
"age": "3"
},
"error": null
}
...that type of thing. So I envision inserting little bits of standard data into every response before it goes over the wire. What's the best way to accomplish this?
TIA.
I believe you can use an ActionFilterAttribute to achieve this kind of behaviour. You would first need to create a class to represent your wrapped response (all the properties are string, adjust as you need):
public class WrappedJsonResponse
{
public string Id {get;set;}
public string ApiVersion {get;set;}
public object Data {get;set;}
public string Error {get;set;}
}
The ActionFilterAttribute allow you to do some processing after the execution of an action via the virtual OnActionExecuted method:
public class WrappedJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext context)
{
// A POCO response will normally be wrapped in an ObjectContent
var content = context.Response.Content as ObjectContent
if(content != null)
{
// Create the WrappedJsonResponse object appropriately and
// put the original result in the Data property
content.Value = new WrappedJsonResponse { Data = content.Value };
content.ObjectType = typeof(WrappedJsonResponse);
}
}
}
With the attribute, you can then choose to apply it where you want (whole controller, action only or as a default filter).
Note: I do not have access to a development environment at the moment and have not tested the filter. If this is not complete, it should at least give you an idea on how it can be done.

Grails Enumeration to JSON

I would like to change the way enums are marshalled to JSON. I am currently using default grails.converters.JSON ( as JSON) and for example in controller I use:
FilmKind.values() as JSON
The output of this is:
"kind":[{"enumType":"FilmKind","name":"DRAMA"},{"enumType":"FilmKind","name":"ACTION"}]
I would like to remove "enumType" and just return:
"kind":["DRAMA","ACTION"]
I am looking for a solution which would still allow me to use
as JSON
because I don't want to marshall each enumeration individually.
In case anyone is wandering how to convert all enum values to plain String values in a generic way:
class EnumTypeHidingJSONMarshaller {
void register() {
JSON.registerObjectMarshaller(Enum) { Enum someEnum ->
someEnum.toString()
}
}
}
because I don't want to marshall each enumeration individually.
Well, unless you want to write your own JSON converter, marshalling is the best approach here. Reason is because the only real other way is to do what Sergio is suggesting and you'll have to call that code everywhere you need it. And if FilmKind is a property of another class then his solution won't work at all really.
I would suggest Marshallers and here is how I would do it. Create a class called FilmKindMarsaller:
class FilmKindMarshaller {
void register() {
JSON.registerObjectMarshaller(FilmKind) { FilmKind filmKind ->
[
name: filmKind.toString()
]
}
}
}
Then add the following to your Bootstrap:
[ new FilmKindMarshaller() ].each { it.register() }
The above is so that you can just keep adding instances of each Marshaller you need.
Now, anytime FilmKind is JSON'ified, be that on its own or part of a parent class, you get the JSON you want, sans enumType.
You can register a custom object marshaller for your domain class to allow as JSON. In your Bootstrap.groovy, you can do something like this:
JSON.registerObjectMarshaller(FilmKind) {
def result = [:]
def props = ['name']
props.each { prop ->
result[prop] = it."$prop"
}
result
}
How about:
def display = [kind:[]]
FilmKind.values().each { val ->
display.kind.add(val.value)
}
render display as JSON

List becomes String when Marshalling object to json

I'm getting a pretty strange error when marshalling my object to json. My object is annotated like this.
My class:
#XmlRootElement(name = "myobject")
public class MyObject {
private List<String> contactPersonsForMyObject;
#javax.xml.bind.annotation.XmlElement()
public List<String> getContactPersonsForMyObject() {
return contactPersonsForMyObject;
}
public void setContactPersonsForMyObject(List<String> contactPersonsForMyObject) {
this.contactPersonsForMyObject = contactPersonsForMyObject;
}
}
Everything works fine except for that if the List contactPersonsForMyObject contains only one value it get's marshalled to a string which ofcourse creates problems since the application consuming this expects a list.
The marshalled object:
[
{
"myobject": {
"somethingcool": "amazing",
"contactPersonsForMyObject": [
"test.test#gmail.com",
"test#test.se"
],
"myObjectId": "c85e48730501bfae41e67714c6131b7d"
}
},
{
"myobject": {
"somethingcool": "cool",
"contactPersonsForMyObject":"test#test2.se",
"myObjectId": "c85e48730501bfae41e67714cqwerty"
}
}
]
Why does this happen and how do I force it to create a list with one value?
Try using Jackson to handle processing your objects into JSON, it solved the same array problem for me in the past. If you are using RESTEasy (version 1.2 GA) with Maven, this link should help you get things setup to use Jackson to serialize objects to JSON.
This article also has some useful information for integrating Jackson with RESTEasy. Hope this helps!

Jackson JSON to Java mapping for same attrubute with different data type

I have a JSON object which I don't have control of and want to map it to a Java object which is pre-created.
There is one attribute in the JSON object which can be a URL or it could be a JSONArray.
Class SomeClass {
private URL items;
public URL getURL() {
return items;
}
public void setURL(URL url) {
this.items = url;
}
}
Below is the JSON:
Case A:
{
...
items: http://someurl.abc.com/linktoitems,
...
}
OR
Case B
{
...
items: [
{ "id": id1, "name": name1 },
{ "id": id2, "name": name2 }
]
...
}
If i create the POJO to map for Case A, Case B fails and vice versa. In short, is there a way to map the JSON attribute to the POJO field with different data types? In that case I will create two separate fields in the POJO named,
private URL itemLink;
private Item[] itemList;
It depends on exact details, but if what you are asking is if it is possible to map either JSON String or JSON array into a Java property, yes this can be done.
Obvious way would be to define a custom deserializer which handles both kinds of JSON input.
But it is also possible to define Java type in such a way that it can be constructed both by setting properties (which works from JSON Object) and have a single-String-arg constructor or static single-String-arg factory method marked with #JsonCreator.
Yet another possibility is to use an intermediate type that can deserialized from any JSON: both java.lang.Object and JsonNode ("JSON tree") instances can be created from any JSON. From this value you would need to do manual conversion; most likely in setter, like so:
public void setItems(JsonNode treeRoot) { .... }
What will not work, however, is defining two properties with the same name.
One thing I don't quite follow is how you would convert from List to URL though. So maybe you actually do need two separate internal fields; and setter would just assign to one of those (and getter would return value of just one).