I am wondering how I can personify the default JSON serialization with JMS on entities.
I have an object A that have a OneToMany relation with an object B.
I expose the object B and attrA from A object.
But instead of having the basic JSON
{'attrA' : 'foo', 'Bs' : {{'attrB' : 'bar'}, {'attrB' : 'baz'}, ...} }
I want to have
{'attrA' : 'foo', 'Bs' : {'bar', 'baz', ...}}
Is it possible to do that?
A way to do this, is to play with serializer's annotations
I assume your value objects looks like this
class Foo
{
/**
* #var string
*/
private $attrA;
/**
* #var ArrayCollection<Bar>
*/
private $bs;
}
class Bar
{
/**
* #var string
*/
private $attrB;
}
Import annotations
First, if you didn't already, you need to import the annotations at the top of your files
use JMS\Serializer\Annotation as Serializer;
Set up a custom getter
By default, JMS serializer gets the property value through reflection. My thoughts go on creating a custom accessor for the serialization.
Note: I assume your bs property is an ArrayCollection. If not, you can use array_map
// Foo.php
/**
* #Serializer\Accessor(getter="getBarArray")
*/
private $bs;
/**
* #return ArrayCollection<string>
*/
public function getBarArray()
{
return $this->getBs()->map(function (Bar $bar) {
return $bar->getAttrB();
});
}
Setting up a custom Accessor(getter) will force the serializer to use the getter instead of the reflection. This way, you can tweak any property value to get it in the format you want.
Result
Serialization of these value objects would result in
{'attrA' : 'foo', 'Bs' : ['bar', 'baz', ...]}
Related
/**
* This is the model class for table "hashtag".
*
* #property string $text
*
* #property TweetHashtag[] $tweetHashtags
* #property Tweet[] $tweets
*/
class Hashtag extends ActiveRecord
{
.........
public function getTweetHashtags()
{
return $this->hasMany(TweetHashtag::className(), ['hashtag_text' => 'text']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getTweets()
{
return $this->hasMany(Tweet::className(), ['id' => 'tweet_id'])->viaTable('tweet_hashtag', ['hashtag_text' => 'text']);
}
}
When I do in some component
$hashtags = Hashtag::find()
->with('tweets')
->where(['text' => $hashtagText])
->all();
foreach($hashtags as $hashtag)
{
print_r($hashtag->tweets);
}
It`s working but why tweets - field accessed only via magic method and how can i fix it? And tweetHashtags working well.
Class Tweet have same relationship but public function getHashtags() working without this problem.
Your question is not clear. Each method on a Component class that start with get (like getName) can be accessed with property form (e.g. name). On special case, relations of Yii's ActiveRecord, if you access to relation by property form, you get results. In fact $this->tweets is a shorthand for $this->getTweets()->all().
P.S: On Yii2 Document http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#accessing-relational-data:
Note: While this concept looks similar to the object property feature,
there is an important difference. For normal object properties the
property value is of the same type as the defining getter method. A
relation method however returns an yii\db\ActiveQuery instance, while
accessing a relation property will either return a yii\db\ActiveRecord
instance or an array of these.
$customer->orders; // is an array of `Order` objects
$customer->getOrders(); // returns an ActiveQuery instance
This is useful for creating customized queries, which is described in the next section.
I have an Entity of mine which I would like to expose in a JSON API that I'm developing, the problem is that in this particular controller, there is just one field which I don't want to expose. Is there a way to exclude it from serialization from within the controller?
I know I can annotate my entity so the serializer just passes by that field, but what happens in all the other cases? This is really the exception.
You can assign each property to a group,
then define that group in a context when serializing
from the controller.
Your entity:
use JMS\Serializer\Annotations as Serializer;
class Comment
{
/** #Serializer\Groups({"main", "secondary"}) */
private $id;
/** #Serializer\Groups({"main", "secondary"}) */
private $title;
/** #Serializer\Groups({"main", "secondary"}) */
private $name;
/** #Serializer\Groups({"main"}) */
private $email;
/** #Serializer\Groups({"main", "secondary"}) */
private $message;
}
Then in your controller
use JMS\Serializer\SerializationContext;
$serializer->serialize(
new Comment(),
'json',
SerializationContext::create()->setGroups(array('secondary'))
);
In this example, the email field is excluded from the serialized data, but only for the group named secondary. You can of course call these groups whatever you like.
when I serialize my object using jmsserializerbundle I get corrent JSON object but one property is empty (it's object type), the definition of this property looks like this:
/**
* #var stdObject
*
* #ORM\Column(name="searchQueryParams", type="object")
* #Expose
* #Type("stdClass")
*/
private $searchQueryParams;
But even if there is an object on this entity the serializer outpus it like this:
{
"id": 10,
"search_query_params": {},
"created": "2013-07-02T10:31:02+0200"
},
Just an empty object, with is not true. This is value of searchQueryParams in DB:
O:8:"stdClass":2:{s:4:"name";s:8:"Greacja2";s:10:"price_from";s:4:"2000";}
Its a well-known bug of jms serializer. You can store data in database as array and serialize later as array.
Changing #Type to "Array" seemed to worked.
simply I want PhpStorm autocomplete my model's attributes when I use find(), findAll(), findByAttributes() etc...
I have a model like:
/**
* member model parameters:
* #property integer $id
* #property integer $city_id
* #property string $e_mail
*/
class Member extends CActiveRecord
{
/**
* #static
* #param string $className
* #return Member
*/
public static function model($className = __CLASS__)
{
return parent::model($className);
}
...
When I use active record methods like:
$member = Member::model()->findByAttributes(array('e_mail'=>'Foo Bar'));
and try to autocomplete when I wrote this:
$member->
It only gives me CActiveRecord's parameters and methods in the list.
I tried to change
/**
* Finds a single active record that has the specified attribute values.
* See {#link find()} for detailed explanation about $condition and $params.
* #param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
* An attribute value can be an array which will be used to generate an IN condition.
* #param mixed $condition query condition or criteria.
* #param array $params parameters to be bound to an SQL statement.
* #return CActiveRecord the record found. Null if none is found.
*/
public function findByAttributes($attributes,$condition='',$params=array())
{...
this method's return param from CActiveRecord to Member, self, parent, $this, child etc...
Autocomplete only worked when it was "Member". But this method is used for all models not just the Member model so this is not a solution.
If anyone knows the solution (preferably without changing the framework core methods) I will be glad.
NOTE: All of my awesome Yii code is freely available on bitbucket in the repositories here and here. If you hate Yii's verbosity, check out my Pii class. I think you'll totally dig it.
I've run into this and what I do is augment the phpdoc for the static model() method. This corrects the issue and the autocomplete works fine.
For instance:
class MyModel extends CActiveRecord {
/**
* #static
* #param string $className
* #return MyModel|CActiveRecord
*/
public static function model($className=__CLASS__) {
.... yada yada yada ...
}
}
Note the pipe and additional class added to the "#return". This tells PhpStorm to also include that class in the auto-complete lookups.
One additional note, if you're using namespaces, you may need a slash in front of some class names. Just depends on your project and includes.
=============== UPDATE: 2013-08-05 ===============
With PhpStorm v6 and up, it looks like you can use:
*
* #return $this
*
And also get proper auto-completion.
On a side note, the whole static "model()" method thing is antiquated (like Yii) and I have a base model class that I use now for all projects. It contains the static model method; which is then no longer required in each of my subclasses. Here's an example...
<?php
namespace My\Awesome\Name\Space;
/**
* MyBaseModel
* etc.etc.
*/
class MyBaseModel extends \CActiveRecord
{
/**
* Returns the static model of the specified AR class.
*
* #param string $className
*
* #return $this
*/
public static function model( $className = null )
{
return parent::model( $className ? : \get_called_class() );
}
//code code code code
}
/**
* MySubModel
*/
class MySubModel extends MyBaseModel
{
/**
* {#InheritDoc}
*/
public function tableName()
{
return 'my_sub_table';
}
}
$_models = MySubModel::model()->findAll( 'xyz = :xyz', array( ':xyz' => 'abc' ) );
if ( !empty( $_models ) )
{
foreach ( $_models as $_model )
{
// Do awesome stuff...
}
}
Autocomplete works fine for all the subclasses...
Just thought I'd update this and let y'all know.
You can use phpdoc #method. You can use this approach for frequently-used models or you can create new template for code generator.
/**
* #method Member findByPk($pk,$condition='',$params=array())
*/
class Member extends CActiveRecord
{
I am new to JSON and Groovy. I can make an AJAX call to a groovlet and make it build some HTML codes with MarkupBuilder. Along with the HTML string being returned, I want a JSON string to be filled in one of a input text box. The problem is the use of JsonGroovyBuilder(). I can't even get the simplest example shown in Json-lib to run correctly. Here is my code:
import net.sf.json.groovy.*;
import net.sf.json.groovy.JsonGroovyBuilder;
def builder = new JsonGroovyBuilder()
def books = builder.books {
book = [title: "Groovy in Action", author: "Dierk Konig"]
book = [title: "Groovy in Action", author: "Dierk Konig"]
}
I run this simple piece of code on GroovyConsole and I get this in return:
Result: {"books":null}
Very odd. But the even "odder" thing is when I run it in Eclipse, I get this:
Caught: groovy.lang.MissingMethodException: No signature of method: net.sf.json.groovy.JsonGroovyBuilder.books() is applicable for argument types: (JSONSandbox$_run_closure1) values: [JSONSandbox$_run_closure1#164debb]
Possible solutions: is(java.lang.Object)
at JSONSandbox.run(JSONSandbox.groovy:6)
I think that I have all the jar files I needed:
json-lib-2.3-jdk15.jar
commons-collections-2.1.jar
commons-lang-2.3.jar
httpclient-4.0.1.jar
I have been stuck in this problem for a couple of days now. Perhaps I have done something wrong, or misunderstanding the use of this function. Everywhere I search on JSON and Groovy points back to Grails. I am still new to Groovy and I don't know Grails. I don't want to throw away my Groovy codes and start over. What could be the fix of this problem? Many many thanks in advance!
I've never tried using JsonGroovyBuilder, so can't really help you with that. I was similar frustrated by the JSON Builder provided by Grails 1.1 (which was replaced with a better version in Grails 1.2). I overcame this frustration by writing my own Groovy JSON builder, which you're welcome to use. I've pasted the source code below.
import org.json.JSONStringer
/**
* An alternative JSON builder, because <code>grails.util.JSonBuilder</code> sucks!
* The reasons why it sucks are described here: http://www.anyware.co.uk/2005/2008/06/19/a-grails-json-builder-that-doesnt-suck/
* Although this page provides an alternative JSON builder, the author didn't provide any usage examples, and I couldn't
* figure out how to use it, so I wrote my own instead.
*
* The underlying JSON functionality is provided by <code>json.org.JSONStringer</code>. An example usage is:
*
* <code>
* builder.array(['feck', 'arse', 'girls']) {
* foo(bar:'1', baz: '2') {
* hobbies(sport: 'snooker', drink: 'guinness')
* emptyObj([:])
* emptyArray([])
* }
* }
* builder.json
* </code>
*
* This will print:
* <code>
* ["feck","arse","girls", {"bar":"1", "baz":"2", "hobbies": {"sport":"snooker", "drink":"guinness"}, "emptyObj": {},"emptyArray":[]}]
* </code>
*
* Verifiable usage examples are provided by the unit tests. A few points worth noting (the term 'element' is used below
* to mean 'either a JSON object or JSON array'):
*
* <ul>
* <li>The nesting of elements is indicated by the nesting of closures in the usual Groovy builder fashion</li>
* <li>The name of the method is used as the name of the key when nesting an element within an object</li>
* <li>The name of the method is irrelevant when nesting an element within an array, but it is recommended
* to use either the method name 'object' or 'array' for the sake of code readability</li>
* <li>The decision to create an array or object is determined by the type of the method parameter. A map will cause
* an object to be created, any other type will cause an array to be created</li>
* <li>To create an empty array or an array whose contents are determined only by nested closures, either call
* <code>builder.array()</code> or <code>builder.keyName([])</code>. The latter should be used when nesting the empty
* array within an object and control over the key name is required.</li>
* <li>To create an empty object or an object whose contents are determined only by nested closures, either call
* <code>builder.object()</code> or <code>builder.keyName([:])</code>. The latter should be used when nesting the empty
* object within another object and control over the key name is required.</li>
* </ul>
*/
class SimpleJSONBuilder extends BuilderSupport {
private jsonText = new JSONStringer()
/**
* Returns the JSON text created by this builder
*/
String getJson() {
jsonText.toString()
}
String toString() {
getJson()
}
protected void setParent(Object parent, Object child) {
// Method is abstract in parent class, but an empty implementation is all we need
}
/**
* Creates an array or object which is either empty, or whose contents are determined exclusively by nested closures.
*/
protected Object createNode(Object name) {
if (current == ElementType.OBJECT) {
throw new IllegalStateException("""Error processing method $name() Empty argument methods should not be invoked
when nesting an element within an object because the key name cannot be determined. Replace this call with either
$name([]) or $name([:])""")
}
if (name == 'array') {
jsonText.array()
return ElementType.ARRAY
} else if (name == 'object') {
jsonText.object()
return ElementType.OBJECT
} else {
throw new IllegalArgumentException("""When calling a method with no arguments, the method must be named either
'$array' or '$object' to indicate which you wish to create""")
}
}
protected Object createNode(Object name, Map attributes, Object value) {
throw new UnsupportedOperationException("Error invoking method $name. Method calls must supply either a single object (to create an array) or a Map (to create an object)")
}
/**
* Ensures that an array/object is correctly nested within an object
* #name Name of the key to use for the nested element
* #return The type of element
*/
private void nestElement(name) {
if (current == ElementType.OBJECT) {
jsonText.key(name)
}
}
/**
* Creates an array
* #name Name of the method. This will be used as the key if the array is nested within an object
* #value The contents of the array. This should be either a single value or a collection or array
* #return The type of element
*/
protected Object createNode(Object name, Object value) {
nestElement(name)
jsonText.array()
if (value instanceof Collection || value instanceof Object[]) {
value.each {jsonText.value(it)}
} else {
jsonText.value(value)
}
return ElementType.ARRAY
}
/**
* Creates an object
* #name Name of the method. This will be used as the key if the object is nested within an object
* #value The name-value pairs contained by this object
* #return The type of element
*/
protected Object createNode(Object name, Map attributes) {
nestElement(name)
jsonText.object()
attributes.each {key, value ->
jsonText.key(key).value(value)
}
return ElementType.OBJECT
}
protected void nodeCompleted(Object parent, Object node) {
node == ElementType.OBJECT ? jsonText.endObject() : jsonText.endArray()
}
}
private enum ElementType {
ARRAY, OBJECT
}
The source code above defines the class SimpleJSONBuilder and the enum SimpleJSONBuilder, but you can store both of these in a single file SimpleJSONBuilder.groovy.
The only library required by this builder is the Java JSON library provided by json.org.
In case the comments in the code above don't explain how to use it sufficiently well, here are some test cases:
public class SimpleJSONBuilderTests extends GroovyTestCase {
void testRootArrayElement() {
def builder = new SimpleJSONBuilder()
builder.array(['feck', 'arse', 'girls']) {
foo(bar: '1', baz: '2') {
hobbies(sport: 'snooker', drink: 'guinness')
emptyObj([:])
emptyArray([])
}
}
assertEquals builder.json, '["feck","arse","girls",{"bar":"1","baz":"2","hobbies":{"sport":"snooker","drink":"guinness"},"emptyObj":{},"emptyArray":[]}]'
}
void testRootObjElement() {
def builder = new SimpleJSONBuilder()
builder.object(feck:'arse') {
foo(bar: '1', baz: '2') {
hobbies(sport: 'snooker', drink: 'guinness')
emptyObj([:])
emptyArray([])
}
}
assertEquals builder.json, '{"feck":"arse","foo":{"bar":"1","baz":"2","hobbies":{"sport":"snooker","drink":"guinness"},"emptyObj":{},"emptyArray":[]}}'
}
/**
* Test that both mechanisms for creating empty arrays are equivalent
*/
void testEmptyArrays() {
def builder = new SimpleJSONBuilder()
builder.array([])
def builder2 = new SimpleJSONBuilder()
builder2.array()
assertEquals builder.json, builder2.json
assertEquals builder.json, "[]"
}
/**
* Test that both mechanisms for creating empty objects are equivalent
*/
void testEmptyObjects() {
def builder = new SimpleJSONBuilder()
builder.object([:])
def builder2 = new SimpleJSONBuilder()
builder2.object()
assertEquals builder.json, builder2.json
assertEquals builder.json, "{}"
}
}
It is a minor bug in JsonGroovyBuilder. If you use it from script, then unknown properties are resolved using script's binding.
I have posted a patch for JsonBuilder, so I hope the bug will be resolved soon.
See https://sourceforge.net/tracker/?func=detail&aid=3022114&group_id=171425&atid=857928 for details.