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
Related
I know in grails i can define diferent JSON marshallers and asign names to them for different uses, which is very nice. However i end with a lot of code in the Bootstrap section and i end with two places where i need to tweak when domain classes changes.
Ithink thi is not good enough and i wonder if it might be possible to define JSON marshallers in the domain class itself.
Do you think would it be a good practice? ... can you provide suggestions on the best approach to achieve this?
Thanks,
I wrote a plugin for this purpose specifically. It allows you to use annotations in domain classes, like this:
import grails.plugins.jsonapis.JsonApi
class User {
static hasMany = [
pets: Pet
]
#JsonApi
String screenName
#JsonApi('userSettings')
String email
#JsonApi(['userSettings', 'detailedInformation', 'social'])
String twitterUsername
#JsonApi(['detailedInformation', 'userSettings'])
Set pets
String neverGetsSerialized
#JsonApi('detailedInformation')
Integer getNumberOfTicklyAnimals() {
pets.count { it.likesTickling }
}
}
In your controller, you would then call JSON.use('detailedInformation') to activate a specific marshaller.
In Bootstrap.groovy write this code:
JSON.registerObjectMarshaller(YourClass) { YourClass yourClass->
Map result = [:]
result['yourClass.property'] = yourClass.property
def domain = new DefaultGrailsDomainClass(YourClass)
domain.persistentProperties.each { GrailsDomainClassProperty property, String propertyName = property.name ->
result[propertyName] = yourClass[(propertyName)]
}
return result
}
Code below add one property, you can name it how u want
result['yourClass.property'] = yourClass.property
This code add all properties by it's name to map:
domain.persistentProperties.each { GrailsDomainClassProperty property, String propertyName = property.name ->
result[propertyName] = yourClass[(propertyName)]
}
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)
}
}
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 )
}
}
I have a simple action such as this:
def showSomething() {
render Color.get(params.id) as JSON
}
This will render all the properties in the Color class as JSON. However, what if I only want to render two properties, say, colorName and shade?
Gjordis has the right option if you want to render the same properties everytime you render the object. However, you could simply do this:
Color color = Color.get(params.id)
render ([colorName: color.colorName, shade: color.shade] as JSON)
This is pretty simple:
def relevantProperties = ["colorName","shade"]
def color = Color.get(params.id)
def reply = relevantProperties.collectEntries { property ->
[property, color[property]]
}
render reply as JSON
and there you are :-)
import grails.converters.JSON
class BootStrap {
def init = {servletContext ->
JSON.registerObjectMarshaller(Color) {
def returnArray = [:]
returnArray['shade'] = it.shade
returnArray['colorName'] = it.colorName
return returnArray
}
}
Somebody can correct me, I have not used grails. But overriding the function called in the conversion is the key.
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