One fellow programmer at the forum was asking how to convert the ActiveRecord model and its relations to a JSON String. He said that other frameworks do that and did some research and found nothing on this subject.
This is why I created this small behavior, to do exactly that.
Requirements ¶
Was developed using Yii 1.1.4.
Usage ¶
Download and unzip its contents to your application's protected/behaviors folder -the zipped package has the behaviors folder within in case you haven't done that.
//
// Once EJsonBehavior is placed in the folder
// We need to configure the behavior() function
// in your model
public function behaviors() {
return array(
'EJsonBehavior'=>array(
'class'=>'application.behaviors.EJsonBehavior'
),
);
}
Then, to convert your AR object to a JSON string is, as said, 'like in other frameworks', one line of code:
echo $model->toJSON();
Thoughts ¶
I think this could be a good starting point to create not only Yii extensions on the server side but also on the client side in form of jQuery plugins that take JSON Yii Objects as part of their configuration steps.
Just like ExtJS javascript library does with all of its objects.
Contribution
Thank you very much for this behavior! I have an addition on how the JSON is generated. I have 2 models, Sale and SaleProducts. Doing $sale->toJSON(); the result is:
{"jsonDataSource":{"attributes":{ ... },"relations":{"products":[...]}}}
So I changed your behaviour to this:
public function toJSON(){ $this->owner = $this->getOwner(); if (is_subclass_of($this->owner,'CActiveRecord')){ $attributes = $this->owner->getAttributes(); $this->relations= $this->getRelated(); foreach($this->relations as $key => $value) { $attributes[$key] = $value; } return CJSON::encode($attributes); } return false; }
And now the result is:
{"id":"1","date":"2010-11-07 11:43:25","clientname":"Test","paymentmethod":"V","deliverydate":null,"totalvalue":"160","products":[{"id":"1","saleID":"1","productcode":"00002","observation":"teste","amount":"10","price":"10","totalvalue":"100"},{"id":"2","saleID":"1","productcode":"00006","observation":"testrefadsgfdsgdsgs","amount":"5","price":"12","totalvalue":"60"}]}
I don't have jsonDataSource, attributes, or relations anymore. I have a plain JSON with attributes and the relations named.
I guess is better doing json.id than json.jsonDataSource.attributes.id, or json.products than json.relations.products.
Cheers!
Nice idea!
I like this!
nice work
brunotavares
Your approach is very good brunotavares.
I've done it with 'jsonDataSource' and 'relations' objets, so to create a naming convention (sort of) just in case there are fellow programmers who wish to create jQuery client plugins with this. As by just including its properties would be much harder than knowing in advance that has a 'jsonDataSource' and 'relations' objects within.
It could be cool to push data in JSON format from the server and CGridView to be totally updated by an AJAX call (imagine this JSON object with other CGridView properties -i.e. columns, headers, and CGridView to be able to update its view)
Nevertheless, great contribution!
Cool!
I just need this, thanks a lot!
Json encode
It seems to go only one relation deep :(
-edit- nevermind I created my own one http://www.yiiframework.com/extension/morray/
thanks for getting me started :)
Well Done
Well done guys!
I would like the Yii development team to amend their core CJSON::encode() function to function like this. Also, Bruno's method is more feasible and easy to parse.
Thank you guys.
Added relations argument to toJSON method to prevent request from hanging on relation with large data
I had to update this to limit the relations if you pass the $rels argument to the toJSON method.
class EJsonBehavior extends CBehavior{ private $owner; private $relations; // $rels should be array of relation names to get public function toJSON($rels=false) { $this->owner = $this->getOwner(); if (is_subclass_of($this->owner,'CActiveRecord')){ $attributes = $this->owner->getAttributes(); $this->relations = $this->getRelated($rels); $jsonDataSource = array('jsonDataSource'=>array('attributes'=>$attributes,'relations'=>$this->relations)); return CJSON::encode($jsonDataSource); } return false; } private function getRelated($rels=false) { $related = array(); $obj = null; $md=$this->owner->getMetaData(); foreach($md->relations as $name=>$relation) { if ($rels) { if (in_array($name,$rels)) $obj = $this->owner->getRelated($name); } else { $obj = $this->owner->getRelated($name); } $related[$name] = $obj instanceof CActiveRecord ? $obj->getAttributes() : $obj; } return $related; } }
and you can now call it like so:
$rels=array(); $rels[]="relNameOne"; $rels[]="relNameTwo"; $json=$model->toJSON($rels);
If the author wants to roll this into an update, it will be backwards compatible and should prevent some requests from hanging. I imagine this will be a common problem in production sites.
Small problem
Hi
Inside my model I've got a function to get some query:
public function getSomeFields($id){ $post = $this->with('fkUsers')->findAll('fk_user_id',array($id)); return $post->toJSON(); //ERROR HERE }
As you see in my code I've got a problem, it doesn't work.
Cheers!
This doesn't work for array of objects, like findAll, findAllByAttributes
Hi,
I have edited it to work it for array of objects.
Behaviour function will return the array for a object and we can create the array for each return and then encode it.
public function toJSON($rels = false) { $this->owner = $this->getOwner(); if (is_subclass_of($this->owner, 'CActiveRecord')) { $attributes = $this->owner->getAttributes(); $this->relations = $this->getRelated($rels); $jsonDataSource = array('attributes' => $attributes, 'relations' => $this->relations); return $jsonDataSource; } return false; }
and in controller
$models=News::model()->findAllByAttributes(array('status'=>1)); $result=array(); foreach ($models as $key=>$model) { $result[$key]= $model->toJSON(); } echo CJSON::encode($result);
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.