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)
}
}
Related
I need to create a JSON response in my application that will contain the object's equivalent + additional fields. Here's how the show() method looks like:
def show(Long id) {
verifyUserLoggedIn()
ScBusinessProcess scBusinessProcess = ScBusinessProcess.get(id)
BusinessProcess businessProcess = BusinessProcessTranslator.toREST(scBusinessProcess)
businessProcess.questions = getQuestions(scBusinessProcess)
businessProcess.rate = getUserRate();
businessProcess.totalSteps = calculateTotalSteps(scBusinessProcess);
businessProcess.usersCurrentStep = 1;
respond businessProcess
}
The toREST() method copies ScBusinessProcess domain class object into a new BusinessProcess Java object. Next ones are the fields I'm adding manually. So far, everything's been working except the last field that's not being shown in the response (debugger claims that the newly created object contains the field and its passed value). Here's my toREST method:
public static BusinessProcess toREST(ScBusinessProcess scBusinessProcess) {
if (scBusinessProcess == null) return null;
return new BusinessProcess(
scBusinessProcess.id(),
scBusinessProcess.getName(),
null,
scBusinessProcess.getDescription(),
scBusinessProcess.getPromoted(),
scBusinessProcess.getLikedCount(),
scBusinessProcess.getDislikedCount(),
0,
0,
0
);
}
And below, the returned JSON:
class "rest.BusinessProcess"
description "Business Process Description"
dislikes 0
id 1
likes 1
name "BusinessProcessOne"
promoted false
questions []
rate 1
totalSteps 0
The usersCurrentStep is missing. What can be the problem? If I need to put more code just say. Also, the Grails version that the application is built on is 3.1.9.
What is the structure of the BusinessProcess class? You can generate JSON of any structure you like if you have the right marshaller - see this link to know more about object marshallers -
http://docs.grails.org/3.0.17/guide/webServices.html#objectMarshallers
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
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 )
}
}
The problem is as follows: I want to handle a POST request with JSON body. The body consists of an array of JSON Objects, without further nesting, i.e. simple HashMaps. All of these objects represent JSON-serialized domain classes from an Android Application, which will have their counterpart in my Grails app. I am thinking of parsing the JSON body, iterating through every element and saving each node as its corresponding domain class instance.
a) How should I save the instance? I am quite new to Grails/Groovy so please excuse any huge mistakes. Code so far is
public static Object JSONArray2Instances(String json, Class type) {
def slurper = new JsonSlurper()
def result = slurper.parseText(json)
//we only want to parse JSON Arrays
if (!(result instanceof JSONArray))
return null
result.each {
def instance = it.asType(type)
// now I need to save to domain class!
}
}
b) where do I place the corresponding code? Currently it is in /grails-app/src/groovy. Where do the tests go? (Since it is not a 'real' Grails component)
c) Is an intermediate command object more appropriate?
Your code should go in to the controller which is handling the request. Please take a look at
gson-grails plugin which has examples of how to serialize and deserialze objects and map them to domain objects. Please take a look at the grails basics where they talk about the conventions used in the grails application and the layout. There are good examples at grails site. Hope this helps
I solved my problem as follows, based on help provided by the comment from allthenutsandbolts. : (Grails-Gson plugin was not needed)
Let N2696AdminAction be the name of a Domain Class
in my controller:
class N2696AdminActionController extends RestfulController{
static responseFormats = ['json', 'xml']
def JSONHandlerService
N2696AdminActionController() {
super(N2696AdminAction)
}
#Override
#Transactional
def save(){
if (request!=null)
JSONHandlerService.instancesfromJSON(request.JSON)
}
}
then I delegate persisting to my service as follows
class JSONHandlerService {
def instancesfromJSON(Object request){
//we only want to parse JSON Arrays
if (!(request instanceof JSONArray))
return null
request.each {
def domainClass = Class.forName("${it.type}",
true, Thread.currentThread().getContextClassLoader())
def newDomainObject = domainClass.newInstance(it)
newDomainObject.save(failOnError:true, flush:true, insert: true)
}
}
}
type is a Json attribute which holds the full (package inclusive) name for my class. This way, I can save to multiple Domain Classes with the same POST request.
Here is a Model
import org.bson.types.ObjectId
class Foo{
ObjectId id
String name
}
And here an action
def action = {
render(status:200, contentType:"application/json") {
['foo' : Foo.get(params.id)]
}
}
The action will return something like this
{"foo":{"class":"Foo","id":{"class":"org.bson.types.ObjectId","inc":340737392,"machine":-2019394572,"new":false,"time":1299107672000},"name":"fooName"]}
My question is, how can I send in the json the toString of the ObjectId, I don't want this
"id":{"class":"org.bson.types.ObjectId","inc":340737392,"machine":-2019394572,"new":false,"time":1299107672000}
I want something more like
"id":18893828183
I know I can select the parameters I want like:
def foo = Foo.get(params.id)
['foo' : 'Foo' :[id:foo.id.toString(), name:foo.name]]
But I don't want to declare always what I want to return as json, I want to return all the object, Foo.get(params.id).encodeAsJSON()...
Is there a way to override encodeAsJSON()
I already tried to add this
class Foo{
....
static transients : ['idStr']
def getIdStr(){
return this.id.toString()
}
....
}
But it's ignored in the encodeAsJSON()
I even tried this
class Foo{
....
def toJSON(){
def obj = this.encodeAsJSON()
def json = new JsonSlurper().parseText(obj);
json.idString = this.id.toString()
return json.toString()
}
...
}
this "works", but no....
because after this
render(status:200, contentType:"application/json") {
['foo' : Foo.get(params.id).toJSON()]
}
the render encode the json, so everything is "escaped"....
So what do you think is the solution, with a builder always defining what I want to return?
Hope, I made my question clear....
I'll start with the builder, hope you can give me another simpler / cleaner solution...
Thanks
edit
I just did a method that returns the object as a map so now I do something like this
render(status:200, contentType:"application/json") {
['foo' : getFooAsMap(Foo.get(params.id))]
}
Register this objectMarshaller at Bootstarp.groovy and it will work like a charm
import grails.converters.JSON
import org.bson.types.ObjectId
JSON.registerObjectMarshaller(ObjectId) {
return it.toStringMongod()
}
If you're going to be JSON-encoding your domain classes out to the web, I wonder if ObjectId might not be the best choice? The GORM/MongoDB integration allows you to use any type for the id. You could just declare it as a String type (which can be assigned as a toString of an ObjectId if you like to use that for its randomness) and then you don't need to worry about this mess. Any performance/scalability problems from this could be analysed/dealt with later, but I wouldn't expect there to be any unless it's a very large app.
Use GStrings in your map and you will get a numeric value for your ObjectId.
E.g.
render ["foo":"$foo.id"] as JSON
You can do this too:
def domainObj = YourDomainClass.get(params.id)
Map props = [:]
def domain = new DefaultGrailsDomainClass(YourDomainClass.class)
domain.properties.each{
props[it.name] = domainObj[it.name]
}
props["id"] = domainObj.id.toString()
render props as JSON
Or better yet, make it reusable. Put this closure someplace handy:
def mongoObjectResponse = {dobj ->
Map props = [:]
def domain = new DefaultGrailsDomainClass(YourDomainClass.class)
domain.properties.each{
props[it.name] = dobj[it.name]
}
props["id"] = dobj.id.toString()
// I like to leave room in my responses for messages and such
message = ""
obj = props
}
Then call like this from your controller:
return render(contentType: "text/json") {
mongoObjectResponse.delegate = delegate
mongoObjectResponse(domainObj)
}
Just define your domain class as
class Foo {
String id
String name
}
Instead of ObjectId