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.
Related
Can someone tell me what I am doing wrong in this post?
I suspect I am posting to my django API incorrectly. I have basically a Question object that has a field for an array of Answers. I can post correctly without the answers, but when I try to add the JsonArray for answers the post fails with the unable to parse error.
LogCat Excerpt
05-21 00:12:52.875 15720-15720/com.pipit.waffle D/ConnectToBackend﹕ {"text":"gf or ed","answers":[{"text":"gf","votes":0,"id":null},{"text":"ed","votes":0,"id":null}],"user_id":"temp user id"}
05-21 00:12:52.875 15720-15720/com.pipit.waffle D/ConnectToBackend﹕ postQuestion called with {MY API} and has errorcom.google.gson.JsonParseException: unable to parse json
05-21 00:12:52.875 15720-15720/com.pipit.waffle D/ConnectToBackend﹕ postQuestion returns result with NULL
Django Side
serializers.py
class EmbeddedAnswerSerializer(serializers.ModelSerializer):
votes = serializers.IntegerField(read_only=True)
picture = serializers.ImageField(read_only=True)
class Meta:
model = Answer
fields = ('id', 'picture', 'text', 'votes',)
class QuestionSerializer(serializers.ModelSerializer):
answers = EmbeddedAnswerSerializer(many=True, source='answer_set')
class Meta:
model = Question
fields = ('id', 'answers', 'created_at', 'text', 'user_id',)
class AnswerSerializer(serializers.ModelSerializer):
text = serializers.CharField(read_only=True)
vote = serializers.BooleanField(required=True)
picture = serializers.ImageField(read_only=True)
votes = serializers.IntegerField(read_only=True)
models.py
class Question(models.Model):
user_id = models.CharField(max_length=36)
text = models.CharField(max_length=140)
created_at = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return u'Question #{}'.format(self.pk)
class Answer(models.Model):
picture = models.ImageField(("Picture"), upload_to=upload_pic_to, blank=True)
question = models.ForeignKey(Question)
text = models.CharField(max_length=25)
votes = models.IntegerField(default=0)
def __unicode__(self):
return u'Answer to question {} ({} votes)'.format(self.question_id, self.votes)
Client Side
(android)
public static void postQuestion(final Context mcontext, Question mquestion){
JsonArray answerarray = new JsonArray();
JsonObject answerjson = new JsonObject();
JsonObject answerjson2 = new JsonObject();
answerjson.addProperty("text", mquestion.getChoices().get(0).getAnswerBody());
answerjson2.addProperty("text", mquestion.getChoices().get(1).getAnswerBody());
answerjson.addProperty("votes", 0);
answerjson2.addProperty("votes", 0);
answerjson.addProperty("id", mquestion.getId());
answerjson2.addProperty("id", mquestion.getId());
answerarray.add(answerjson);
answerarray.add(answerjson2);
JsonObject json = new JsonObject();
json.addProperty("text", mquestion.getQuestionBody());
json.add("answers", answerarray);
json.addProperty("user_id", "temp user id");
final String url = "my endpoint";
Ion.with(mcontext) //Ion Koush is just a library for making async android requests to a URL - doubt this is the problem
.load(url)
.setJsonObjectBody(json)
.asJsonObject()
.setCallback(new FutureCallback<JsonObject>() {
#Override
public void onCompleted(Exception e, JsonObject result) {
if (e != null){
Log.d("ConnectToBackend", "postQuestion called with "+url+" and has error" + e.toString());
if (result==null){
Log.d("ConnectToBackend", "postQuestion returns result with NULL");
}
}
else{
//Do Stuff
}
}
});
}
This is an example of what a successful GET to the same endpoint looks like
{"id":5,"answers":[{"id":10,"picture":"someurl","text":"microsoft","votes":0},{"id":9,"picture":"someurl","text":"apple","votes":0}],"created_at":"2015-03-15T04:14:00.782903Z","text":"MicroSoft or Apple","user_id":"8"}
Are you sure it's not actually a string that is successful rather than a dictionary? I've ran into this problem on the Django side, don't know much about Android.
With python it would be:
import json
your_dict = {...}
# this stringifies the dict
json.dumps(your_dict)
I know you're doing this on the client, so the code above won't be your answer, but it's an idea and hopefully it helps!
You can try this in an alternative way by changing the QuestionSerializer:
class QuestionSerializer(serializers.ModelSerializer):
answers = serializers.SerializerMethodField()
class Meta:
model = Question
fields = ('id', 'answers', 'created_at', 'text', 'user_id',)
def get_answers(self,obj):
ans_set = AnswerSerializer(Answer.objects.filter(question=obj),many=True)
return ans_set
I think that the data is not well-formed for that service....
{"text":"gf or ed","answers":[{"text":"gf","votes":0,"id":null},{"text":"ed","votes":0,"id":null}],"user_id":"temp user id"}
'Picture' doesn't exist, you don't send it from android... And Django makes fields 'required' by default, so it should be causing your problem ...
Have you got any traces from python? Try to debug it.
You are missing your views.py code here, but I am guessing you are using ListCreateAPIView out of the box. It turns out that django rest framework does not support automatically creating nested objects out of the box, so you'll need to add your own code to address that functionality. There is good documentation here:
http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
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 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
I'm trying to render some response like this
def doAjax = Action { request =>
object MyResult {
val resultCode = 0
val resultTextMessage = "sss"
}
Ok(Json(MyResult)) // It's not working anymore - not compiling in v2.0!
}
but how to map my object (MyResult) to JSON with Play 2.0?
In Play 1.0 with scala module I did successfully the following:
def dosomeaj = {
object MyResult{
val resultCode = 0
val resultTextMessage = "sss"
}
Json(MyResult) // It's working in 1.0
}
EDIT2
New Wiki link for v2.1. The old link below is not working anymore.
EDIT
We'll all be happy to read the new Wiki entry for this point. Check this out
PREVIOUS
Here is the comment from the community about the state of Json support in play 2.0.
link to Post
They are moving from Jackson to a philosophy inspired by SJSON that offers more control on the un/marshalling, that brings facilities to manage them, w/o the overhead of Reflection (which I agree with them is a pain for performance and is fragile against Class changes...)
So here is what you can read on the post:
case class Blah(blah: String)
// if you want to directly serialize/deserialize, you need to write yourself a formatter right now
implicit object BlahFormat extends Format[Blah] {
def reads(json: JsValue): Blah = Blah((json \ "blah").as[String])
def writes(p: Blah): JsValue = JsObject(List("blah" -> JsString(p.blah)))
}
def act = Action { implicit request =>
// to get a Blah object from request content
val blah = Json.parse(request.body.asText.get).as[Blah]
// to return Blah as application/json, you just have to convert your Blah to a JsValue and give it to Ok()
Ok(toJson(blah))
}
In the second link (SJSON), I propose you to pay special attention to the generic formatting possible by using case class and their deconstruction method (unapply).
Play 2 comes with Jerkson
case class Blah(blah: String)
import com.codahale.jerksHon.Json._
def act = Action { implicit request =>
Ok(generate(parse[Blah](request.body.asText.get))).as("application/json")
}
This code will deserialize and reserialize the json.
For more information https://github.com/codahale/jerkson
I've found this solution in Play integration tests right now.
It's require to define in app/models/MyResult2.scala with this content:
case class MyResult2(resultCode: Int, resultTextMessage: String)
object Protocol {
implicit object MyResult2Format extends Format[MyResult2] {
def writes(o: MyResult2): JsValue = JsObject(
List("resultCode" -> JsNumber(o.resultCode),
"resultTextMessage" -> JsString(o.resultTextMessage)
)
)
def reads(json: JsValue): MyResult2 = MyResult2(
(json \ "resultCode").as[Int],
(json \ "resultTextMessage").as[String]
)
}
}
And after this you can use it in your controller class like this:
import play.api._
import play.api.mvc._
import play.api.libs.json._
import models._
import models.Protocol._
object Application extends Controller {
def doAjax = Action { request =>
Ok(toJson(MyResult2(0, "Ney")))
}
}
It's now required some manual static marshalling code.
You can use the 'play.api.mvc.as'
def demo = Action {
....
Ok(jsonString).as("text/json")
}
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