Grails 3 JSON Views custom rendering one-to-many associations - json

Okay I have tried everything I know to custom render a one-to-many association with JSON views and failed miserably.
Here's what part of my ticket domain looks like (All good here) ...
class TttTicket {
String title
String number
String description
TttUser assignee
String priority
String status
TttUser creator
Date dateCreated
Date lastUpdated
static mappedBy = [subscribers : 'none', creator:'none']
static belongsTo = [project:TttProject, creator:TttUser]
static hasMany = [subscribers: TttUser]
... blar blar
}
Here's my associated gson rendering template...
import ttt_api_server.TttTicket
model {
TttTicket tttTicket
}
json g.render(tttTicket, [excludes:['creator','subscribers','project']]){
creator {
id tttTicket.creator.id
name tttTicket.creator.name
email tttTicket.creator.email
}
project{
id tttTicket.project.id
name tttTicket.project.name
}
}
... which is working nicely so far. I want to now restricted the output of the properties for each of the subscribers.
How do I rotate over these? For example...
import ttt_api_server.TttTicket
model {
TttTicket tttTicket
}
json g.render(tttTicket, [excludes:['creator','subscribers','project']]){
creator {
id tttTicket.creator.id
name tttTicket.creator.name
email tttTicket.creator.email
}
subscibers g.render(){
tttTicket.subscribers.each {sub ->
return {
name sub.name
}
}
}
project{
id tttTicket.project.id
name tttTicket.project.name
}
}
This does not seem to be documented anywhere. I would like to controller the JSON out put of each subscriber at this level. Not at the domain level as I may need to change the property output depending on my JSON requirements.
Please help :-(

If I understand this correctly, when you do a REST call on ticket, you wanted the domain object that is inside the hasMany relationship to show a specified properties (in this case its subscribers). But when you call directly on the subscribers, you wanted all the properties.
You can create a map first and then render the objects based on the map created (have a look at this).
In your case:
import ttt_api_server.TttTicket
model {
TttTicket tttTicket
}
json g.render(tttTicket, [excludes:['creator','subscribers','project']]){
creator {
id tttTicket.creator.id
name tttTicket.creator.name
email tttTicket.creator.email
}
def s = tttTicket.subsribers.collect {
[
id: it.id,
name: it.name
]
}
subscribers s //this part is doing the rendering
project{
id tttTicket.project.id
name tttTicket.project.name
}
}

If this is the way you want this object to be rendered whenever it's rendered as JSON, I suggest implementing ObjectMarshaller<JSON>:
class TttTicketMarshaller implements ObjectMarshaller<JSON> {
#Override
boolean supports(Object object) {
return object instanceof TttTicket
}
#Override
void marshalObject(Object object, JSON converter) throws ConverterException {
TttTicket ticket = (TttTicket)object
def jsonWriter = converter.writer
jsonWriter.object()
jsonWriter.key("title")
jsonWriter.value(ticket.title)
//other fields
jsonWriter.key("creator")
jsonWriter.object()
jsonWriter.key("id")
jsonWriter.value(ticket.creator.id)
//other creator fields
jsonWriter.endObject()
}
}
I'm not 100% sure about the object() and endObject() functionality, but that appears to be the correct use.
Once you have this, register this in your BootStrap.groovy:
def init = { servletContext ->
JSON.registerObjectMarshaller(new TttTicketMarshaller())
}
With all this in place, you can simply use
render myTicket as JSON
to return the JSON representation of the object as defined by your marshaller

Related

Posting two different JSON objects

I Am Using Spring Boot in which i want a method to get details of person with its land details .
A person can have any number of lands.
I have created a following method but it is not working
#PutMapping("/fl-details/{f}/{l}")
#Timed
public ResponseEntity<PersonDetailsDTO> updateDetails(#RequestParam("f") PersonDetailsDTO personDetailsDTO,
#RequestParam("l") List<LandDetailsDTO> landDetailsDTOS) throws URISyntaxException {
if (personDetailsDTO.getId() == null || landDetailsDTOS.iterator().next().getId() == null) {
return createFarmerDetails(personDetailsDTO,landDetailsDTOS);
}
PersonDetailsDTO result = personDetailsService.save(personDetailsDTO);
landDetailsDTOS.stream().forEach(landDetailsService::save);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(ENTITY_PERSON+ENTITY_LAND, personDetailsDTO.getId().toString()))
.body(result);
}
I can post person details from
#ResponseBody PersonDetailsDTO personDetailsDTO
but i am getting confused in post both of these.
Could anyone tell me how could i post two different Kind Of JSON objects in Spring Boot API
A #RequestParam cannot be deserialised to a PersonDetailsDTO or LandDetailsDTO (unless you implement a custom converter) so in order to pass a complex object (whether it is of type PersonDetailsDTO or type LandDetailsDTO) you must use #ResponseBody.
And since a controller method can only have one #ResponseBody you'll have to pass in a composite object i.e. one which could contain either a PersonDetailsDTO or a LandDetailsDTO. For example:
class CompositeDto {
private PersonDetailsDTO personDetails;
private List<LandDetailsDTO> landDetails
}
And then interrogate this inside your controller method, for example:
if (compositeDto.containsPersonDetails()) {
// ...
} else if (compositeDto.containsLandDetails()) {
// ...
}

Marshal / unmarshal domain instance to JSON in Grails

I'm trying to marshal and unmarshal a domain (e.g. OrgUnit) instance as JSON to a database field like this:
class OrgUnit{
String name
OrgUnit parent
static hasMany = [children:orgUnit]
}
class History{
String data
}
class OrgUnitService{
History marshal(OrgUnit orgUnit){
return new History([
data : (orgUnit.properties as JSON).toString()
]).save()
}
OrgUnit unmarshal(History history){
return OrgUnit.newInstance( JSON.parse(history.data))
}
}
It works fine for simple fields like name, but fields like children are empty in the unmarshaled object.
The history.data field contains children information like this:
{"name":"b","children":[{"class":"demo.OrgUnit","id":3,"children":null,"name":"c"}]}
I'm using Grails 2.2.4.
Any suggestions !?
Update
I tested it on Grails 2.4.3. It works as expected. The content of the history.data field is in both Grails versions identical. The issue is in the unmarshaling part.
The critical part is the creation of a domain instance by properties map. It is used in the default save action in controllers. It might be a security issue in controller, but I take it out of scope there.
In Grails version 2.2.4 it doesn't bind the referencies to the instance.
The workaround there is, to do it manually like this:
class HistoryService {
def grailsApplication
History marshal(OrgUnit orgUnit) {
return new History([
data: (orgUnit.properties as JSON).toString()
]).save(failOnError: true)
}
OrgUnit unmarshal(History history) {
def data = JSON.parse(history.data)
OrgUnit instance = OrgUnit.newInstance(data)
def domainClass = grailsApplication.getDomainClass(OrgUnit.class.name)
domainClass.persistentProperties.each{p->
if (p.oneToMany || p.manyToMany){
def refDataList = data."$p.name"
refDataList.each{refData->
instance."$p.name" << getDomainClass(refData.class).read(refData.id as Long)
}
}else if (p.manyToOne || p.oneToOne){
def refData = data."$p.name"
if(refData && refData.class && refData.id){ // if reference field has value null
instance."$p.name" = getDomainClass(refData.class).read(refData.id as Long)
}
}
}
return instance
}
private def getDomainClass(String domainName){
return grailsApplication.classLoader.loadClass(domainName)
}
}

Grails: setting transient fields in the map constructor

I'm trying to persist Maps of properties as single JSON-encoded columns, as shown in this question.
The problem I'm having is that apparently transient properties cannot be set in the default map constructor. Given any transient field:
class Test {
//...
String foo
static transients = ['foo']
}
It seems that the map constructor (which Grails overrides in various ways) simply discards transient fields:
groovy:000> t = new Test(foo:'bar')
===> Test : (unsaved)
groovy:000> t.foo
===> null
While direct assignment (through the setter method) works as expected:
groovy:000> c.foo = 'bar'
===> bar
groovy:000> c.foo
===> bar
Is there a way to make the map constructor accept transient fields?
Or rather: is there a better way to persist a Map as a single JSON-encoded DB field, rather than the method shown in the linked question?
Here's the complete example:
import grails.converters.JSON
class JsonMap {
Map data
String dataAsJSON
static transients = ['data']
def afterLoad() { data = JSON.parse(dataAsJSON) }
def beforeValidate() { dataAsJSON = data as JSON }
}
I can set data using the setter (which will then be converted into dataAsJSON) but not using the map constructor.
The map constructor in GORM uses the data binding mechanism, and transient properties are not data-bindable by default. But you can override this using the bindable constraint
class Test {
//...
String foo
static transients = ['foo']
static constraints = {
foo bindable:true
}
}
I've also replied to your original question, that you don't need json conversion to achieve what you need. However, If you need json conversion badly, why don't you implement it in your getters/setters?
class Test {
String propsAsJson
static transients = ['props']
public Map getProps() {
return JSON.parse(propsAsJson)
}
public void setProps(Map props) {
propsAsJson = props as JSON
}
}
//So you can do
Test t = new Test(props : ["foo" : "bar"])
t.save()
In this way you encapsulate the conversion stuff, and in DB you have your properties as Json.
You can simplify your case by adding the JSON-conversion methods to your domain class, they should have nothing to do with GORMing:
class Test {
String title
void titleFromJSON( json ){
title = json.toStringOfSomeKind()
}
def titleAsJSON(){
new JSON( title )
}
}

Can the GXT JsonReader (using AutoBeans) parse complex JSON structures in one request?

I need to work with data returned from a service which has a more complex JSON structure than the examples provided in the GXT documentation and thus far I cannot find any instructions or example which demonstrates how this might be accomplished.
The JSON contains multiple key/value pairs, but some of the key/value pairs are collections. I can have all of the data returned to me in one call from the service in the proper structure, but there does not appear to be a way to parse the data into separate entities. In my particular case I am attempting to configure a loader which will process one of the collections but I also need other key/value pairs from the same message (it is not ok to have the loader make one call and then have another call made for the same data and retrieve the other key/value pairs). Is there any way to accomplish this using GXT3?
Example: let's assume I can make a request from a server which returns JSON containing the name of an author along with a collection of the books the author has written. I want to display the author's name above a grid which lists the books. I want only one request made to the server and then have my view display the author in one component and the book list in a grid. Assume I need a loader instead of just a store as the grid may have to make additional calls (e.g. if it is a paging grid, livegrid, etc.).
Example JSON: (one JSON message returned with and author element along with a collection of book elements - I've indented the JSON to illustrate the structure)
{ "returnData" :
{"author" : "AuthorName"},
{"books" :
{"id" : "1", "name" : "Book1"},{"id" : "2", "name" : "Book2"}
}
}
Using the example for JsonReader (see the javadoc for an example) I can receive the request and parse the links into a collection using AutoBeans. This works fine when I need to have those retrieved and parsed in a loader. However, if I do that then the other properties are ignored. I currently don't see any way to parse the other values in the same request so they can be used elsewhere. My example code for the collection processing is below:
// this is the root JSON object, the AuthorRecord
public interface AuthorRecord {
#PropertyName(value="author")
String getAuthor();
#PropertyName(value="author")
void setAuthor(String author);
#PropertyName(value="books")
List<Book> getBooks();#
#PropertyName(value="books")
void setBooks (List<Book> books);
}
// models the book objects returned
public interface Book {
#PropertyName(value="id")
String getId();
#PropertyName(value="id")
void setId(String id);
#PropertyName(value="name")
String getName();
#PropertyName(value="name")
void setName(String name);
}
public interface ReturnData {
AuthorRootObject getAuthorRoot();
}
public interface LibraryAutoBeanFactory extends AutoBeanFactory {
AutoBean<ReturnData> authorRecord();
AutoBean<ListLoadConfig> loadConfig();
}
public class ReturnDataJsonReader extends JsonReader<ListLoadResult<Book>,
ReturnData> {
public ReturnDataJsonReader(AutoBeanFactory factory,
Class<ReturnData> rootBeanType) {
super(factory, rootBeanType);
}
#Override
protected ListLoadResultBean<Book> createReturnData(Object loadConfig,
ReturnData incomingData) {
return new ListLoadResultBean<Book>(incomingData.getBooks());
}
}
The problem I was having was that I need to have a view that includes a grid (paging grid, etc.) which lists out the books, while having the Author's name sit above the grid. I wanted to get all of this information (or at least the first page of results) with only one request to the server since the JSON message contains all the information I need to accomplish this. The problem is that the loader makes the request and receives the response, and it expects that the reader it will use is going to process a collection. In my case, I need the loader to process the collection of books but also populate another data field. The solution I found was to create an arbitrary collection to pass to the loader and then implement my own load handler to process the return object as needed.
1.The JSON being returned is really just one object of type ReturnData. The extended JsonReader could process this using AutoBeans, but if the reader is to be used for the loader, it needs to return a collection. Therefore, override the createReturnData() method to return a collection of one object.
public class ReturnDataJsonReader extends JsonReader<ListLoadResult<AuthorRecord>,
ReturnData> {
public ReturnDataJsonReader(AutoBeanFactory factory, Class<ReturnData> rootBeanType)
{
super(factory, rootBeanType);
}
#Override
protected ListLoadResultBean<AuthorRecord> createReturnData(Object loadConfig,
ReturnData incomingData) {
List<AuthorRecord> authorDataCollection = new ArrayList<AuthorRecord>();
authorDataCollection.add(incomingData);
return authorDataCollection;
}
}
2.The LoadHandler used in the examples takes a ListStore as an input and populates it with the results from the loader. Since the return object is not what we want populating the loader, and since we need to populate another property on the view, create your own LoadHandler to take the objects needed as input and populate them:
View Class Example:
public class ExampleViewClass {
// truncating most of the code in here for brevity
// note some of the objects referenced here reference objects in the question
private String authorName;
private ListStore<Book> bookList;
// IMPORTANT - create your own LoadHandler
private class LibraryLoadResultistStoreBinding<C, M, D extends
ListLoadResult<AuthorRecord>> implements LoadHandler<ListLoadConfig,
ListLoadResult<AuthorRecord>> {
private final ListStore<Book> bookStore;
private final String authorName;
public LibraryLoadResultistStoreBinding(ListStore<Book> books, String author) {
this.bookStore = books;
this.authorName = author;
}
#Override
public void onLoad(LoadEvent<ListLoadConfig, ListLoadResult<AuthorRecord> event)
{
// the response object
AuthorRecord response = event.getLoadResult().getData().get(0);
bookStore.replaceAll(response.getBooks());
author = response.getAuthor();
}
}
// example uses an HttpProxy but that's not required
public void populateView() {
LibraryAutoBeanFactory factory = GWT.create(LibraryAutoBeanFactory.class);
ReturnDataJsonReader reader = new ReturnDataJsonReader(factory, ReturnData.class);
String path = "http://path.to.resource/getinfo";
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
HttpProxy<ListLoadConfig> proxy = new HttpProxy<ListLoadConfig>(builder);
final ListLoader<ListLoadConfig, ListLoadResult<AuthorRecord>> loader = new
ListLoader<ListLoadConfig, ListLoadResult<AuthorRecord>> (proxy, reader);
loader.useLoadConfig(ReturnDataAutoBeanFactory.instance.loadConfig().as();
loader.addLoadHandler(new LibraryLoadResultistStoreBinding<ListLoadConfig,
AuthorRecord, ListLoadResult<AuthorRecord>>(bookList, authorName);
// pass in the objects to be populated
loader.load(); // fire the loader
}

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).