How to JSON serialize relationship collection in Laravel - json

I'm trying to fetch a collection of members through my model Agency (hasMany()).
members is related to an Agency through a hasMany() relationship, and I'm fetching them like so:
$members = $this->model->where("id", $agencyId)->firstOrFail()->members();
In my Member.php model class I'm implementing JSONSerializable with the following method:
public function jsonSerialize()
{
$agency = $this->agency()->first();
return [
'member_id' => $this->member_id,
'name' => $this->name(),
'surname' => $this->surname(),
'email' => $this->email(),
'active' => $this->active,
'agency' => [
'id' => $agency->id,
'name' => $agency->name,
]
];
}
However this seriazation is never being called when I try to fetch the members like so:
$members = $this->model->where("id", $agencyId)->firstOrFail()->members();
return $members->paginate(
$pageSize,
['*'],
'page',
$pageNumber
);
The response is:
"current_page": 1,
"data": [
{
"member_id": "0315c19f-8f5c-467a-97fd-a0422f5e14a5",
"user_id": "77b36a51-4b86-34be-9a76-856ca68c3a1e",
"agency_id": "d8c0cfab-686c-3b40-90ea-8bf52f07a3a0",
"active": true,
"deleted_at": null,
"created_at": "2020-04-30 15:20:38",
"updated_at": "2020-04-30 15:20:38"
},
{
"member_id": "f522914d-17cd-470d-a5d6-365930958a29",
"user_id": "994ca229-554e-3ae9-8459-24dc1479d75c",
"agency_id": "d8c0cfab-686c-3b40-90ea-8bf52f07a3a0",
"active": true,
"deleted_at": null,
"created_at": "2020-04-30 15:20:38",
"updated_at": "2020-04-30 15:20:38"
}
],
"first_page_url": "\/agency\/d8c0cfab-686c-3b40-90ea-8bf52f07a3a0\/members?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "\/agency\/d8c0cfab-686c-3b40-90ea-8bf52f07a3a0\/members?page=1",
"next_page_url": null,
"path": "\/agency\/d8c0cfab-686c-3b40-90ea-8bf52f07a3a0\/members",
"per_page": 50,
"prev_page_url": null,
"to": 2,
"total": 2
}
Why are the timestamps etc. shown in the member data? Why isn't the jsonSerialize method being used?

I think Laravel uses the toArray() function for JSON serialization. You should look into Resources if you really want to control the output of your JSON.
https://laravel.com/docs/7.x/eloquent-resources

Related

How to lower levels in json?

I have a component of vuejs and my problem is that when I want a roles.name Roles works, but I don't want all JSON.
A example of JSON:
{
"id_role": 2,
"name": "Prova",
"email": "prova#prova.com",
"email_verified_at": null,
"created_at": "2021-03-01T09:39:42.000000Z",
"updated_at": "2021-03-01T09:39:42.000000Z",
"roles": [
{
"id": 2,
"name": "infomanager",
"guard_name": "web",
"created_at": "2021-03-01T09:39:42.000000Z",
"updated_at": "2021-03-01T09:39:42.000000Z",
"pivot": {
"model_id": 4,
"role_id": 2,
"model_type": "App\\Models\\User"
}
}
]
}
And this is component of vuejs:
export default {
data() {
return {
items: [],
sortBy: '',
sortDesc: false,
output: null,
// Note 'isActive' is left out and will not appear in the rendered table
fields: [
{
key: 'name',
label: 'Nom Usuari',
sortable: true
},
{
key: 'roles.name',
label: 'Nom Rols',
sortable: true
},
{
key: 'actions',
label: 'Accions'
}
],
}
},
My question is how am I down level, roles.name doesn't work.
Thank you so much!!
If I understand your question, you want to replace your roles array-of-objects to an array which holds only roles.name field.
In this case you can use flatMap method from lodash library.
Assume that your JSON is assigned into a variable called rolesJson, it will look like that:
let rolesJson = {
"id_role": 2,
"name": "Prova",
"email": "prova#prova.com",
"email_verified_at": null,
"created_at": "2021-03-01T09:39:42.000000Z",
"updated_at": "2021-03-01T09:39:42.000000Z",
"roles": [
{
"id": 2,
"name": "infomanager",
"guard_name": "web",
"created_at": "2021-03-01T09:39:42.000000Z",
"updated_at": "2021-03-01T09:39:42.000000Z",
"pivot": {
"model_id": 4,
"role_id": 2,
"model_type": "App\\Models\\User"
}
}
]
};
// create a new attribute with the flatted roles array
rolesJson.flatted_roles = _.flatMap(rolesJson.roles, el => {
return el.name;
});
// remove the original roles array
delete rolesJson.roles;

Kendo Grid .NET Core Template with JSON

I have a Kendo grid template that looks like this...
#(Html.Kendo().Grid<AuditDictionaryViewModel>()
.Name("ConferenceCourseAuditGrid")
.Columns(columns =>
{
columns.Template("#:Course.CourseName#").Title("Course Name");
columns.Template("#:IsDeleted#").Title("Is Deleted");
columns.Bound(c => c.CreatedBy).Title("Created By");
columns.Bound(c => c.CreateDateTime).Format("{0:MM/dd/yyyy hh:mm:ss tt}").Title("Create Date Time");
columns.Bound(c => c.UpdatedBy).Title("Updated By");
columns.Bound(c => c.UpdateDateTime).Format("{0:MM/dd/yyyy hh:mm:ss tt}").Title("Update Date Time");
}).Sortable()...
Where it says Course.CourseName...I'm trying to access a nested property on one of my JSON properties. The JSON that I'm getting back from the Network tab looks like this...
{
"Data": [{
"CreateDateTime": "2020-10-21T10:44:33.6716231-04:00",
"UpdateDateTime": null,
"CreatedBy": "hao.nguyen#kizan.com",
"UpdatedBy": null,
"ConferenceId": 1,
"CourseId": 1,
"IsDeleted": false,
"Course": "{\"LevelId\":5,\"Level\":null,\"CourseName\":\"Course Name...2\",\"CourseDescription\":\"Course Description...2\",\"IsRequired\":true,\"CreditHours\":2.0000,\"IsElective\":false,\"IsLocallyApproved\":false,\"TreatAsLevel5Elective\":false,\"OtherProvider\":false,\"NameProvider\":\"\",\"DisplayOrder\":2,\"IsCharterCourse\":false,\"IsNewCourse\":true,\"MandatoryTrainingTopicId\":null,\"MandatoryTrainingTopic\":null,\"MandatoryCheck\":false,\"CharterTopicCourses\":null,\"TopicCourses\":null,\"ConferenceCourses\":null,\"CreatedBy\":\"unknown\",\"UpdatedBy\":null,\"CreateDateTime\":\"2020-10-19T15:48:40.8230887\",\"UpdateDateTime\":null,\"Id\":1}"
}, {
"CreateDateTime": "2020-10-21T10:44:34.7188373-04:00",
"UpdateDateTime": null,
"CreatedBy": "hao.nguyen#kizan.com",
"UpdatedBy": null,
"ConferenceId": 1,
"CourseId": 3,
"IsDeleted": false,
"Course": "{\"LevelId\":null,\"Level\":null,\"CourseName\":\"Course Name...4\",\"CourseDescription\":\"Course Description...4\",\"IsRequired\":false,\"CreditHours\":0.5000,\"IsElective\":true,\"IsLocallyApproved\":false,\"TreatAsLevel5Elective\":false,\"OtherProvider\":false,\"NameProvider\":\"\",\"DisplayOrder\":4,\"IsCharterCourse\":false,\"IsNewCourse\":false,\"MandatoryTrainingTopicId\":null,\"MandatoryTrainingTopic\":null,\"MandatoryCheck\":false,\"CharterTopicCourses\":null,\"TopicCourses\":null,\"ConferenceCourses\":null,\"CreatedBy\":\"unknown\",\"UpdatedBy\":null,\"CreateDateTime\":\"2020-10-19T15:48:40.8230887\",\"UpdateDateTime\":null,\"Id\":3}"
}, {
"CreateDateTime": "0001-01-01T00:00:00",
"UpdateDateTime": "2020-10-22T14:46:28.5255395-04:00",
"CreatedBy": null,
"UpdatedBy": "hao.nguyen#kizan.com",
"ConferenceId": 1,
"CourseId": 2,
"IsDeleted": true,
"Course": "{\"LevelId\":null,\"Level\":null,\"CourseName\":\"Course Name...3\",\"CourseDescription\":\"Course Description...3\",\"IsRequired\":false,\"CreditHours\":3.0000,\"IsElective\":true,\"IsLocallyApproved\":false,\"TreatAsLevel5Elective\":false,\"OtherProvider\":false,\"NameProvider\":\"\",\"DisplayOrder\":3,\"IsCharterCourse\":false,\"IsNewCourse\":false,\"MandatoryTrainingTopicId\":null,\"MandatoryTrainingTopic\":null,\"MandatoryCheck\":false,\"CharterTopicCourses\":null,\"TopicCourses\":null,\"ConferenceCourses\":null,\"CreatedBy\":\"unknown\",\"UpdatedBy\":null,\"CreateDateTime\":\"2020-10-19T15:48:40.8230887\",\"UpdateDateTime\":null,\"Id\":2}"
}, {
"CreateDateTime": "2020-10-21T10:44:34.7188373",
"UpdateDateTime": "2020-10-22T14:46:29.0453815-04:00",
"CreatedBy": "hao.nguyen#kizan.com",
"UpdatedBy": "hao.nguyen#kizan.com",
"ConferenceId": 1,
"CourseId": 3,
"IsDeleted": true,
"Course": "{\"LevelId\":null,\"Level\":null,\"CourseName\":\"Course Name...4\",\"CourseDescription\":\"Course Description...4\",\"IsRequired\":false,\"CreditHours\":0.5000,\"IsElective\":true,\"IsLocallyApproved\":false,\"TreatAsLevel5Elective\":false,\"OtherProvider\":false,\"NameProvider\":\"\",\"DisplayOrder\":4,\"IsCharterCourse\":false,\"IsNewCourse\":false,\"MandatoryTrainingTopicId\":null,\"MandatoryTrainingTopic\":null,\"MandatoryCheck\":false,\"CharterTopicCourses\":null,\"TopicCourses\":null,\"ConferenceCourses\":null,\"CreatedBy\":\"unknown\",\"UpdatedBy\":null,\"CreateDateTime\":\"2020-10-19T15:48:40.8230887\",\"UpdateDateTime\":null,\"Id\":3}"
}],
"Total": 4,
"AggregateResults": null,
"Errors": null
}
The other columns show up fine, but retrieving the nested properties from Course doesn't work. I think I need a way to escape the quotes from the nested property or something in order to get that value?
The Course property seems to be an encoded JSON, try to decode it:
columns.Template("# let course = JSON.parse(data.Course); ##: course.CourseName#").Title("Course Name");
A better approach is to use DataSource's schema.parse event. Create a custom DataSource like:
#(Html.Kendo().Grid<OrderViewModel>()
.Name("grid")
.DataSource(dataSource => dataSource
.Custom()
.Schema(schema => schema
.Parse(#<text>function (data) {
data.forEach(item => item.Course = JSON.parse(item.Course));
return data;
}</text>)
)
)
)
Then you can use columns.Template("#:Course.CourseName#") on your template.

Angular http get data and put the json object to a variable

I want to get the nested data in officeid id , code ,name ,shortname, accroym . and put it into individual variable.
How to I do that ???
My code:
{
"id": 1,
"code": "1000-001-1-01-001-001",
"name": "PEACE AND ORDER PROGRAM",
"isActive": true,
"majorFinalOutput": null,
"officeId": 1,
"office": {
"id": 1,
"code": "1-01-001",
"name": "Office of the Governor",
"shortName": "PGO",
"accronym": "PGO",
"website": null,
"email": null,
"telephone": null,
"fax": null,
"type": "1"
},
"sectorId": 1,
"sector": {
"id": 1,
"name": "General Public Services Sector",
"code": "1000",
"parentId": null,
"parent": null
},
"dateCreated": "2018-10-02T14:23:04.913",
"dateModified": null,
"createdBy": null,
"modifiedBy": null
}
getProgram() {
return this.httpClient.get('api/programs/' + idhold).subscribe((holdprogram: any[]) => {
console.log(holdprogram);
});
return this.programService.editProgram().finally( () => {
}).subscribe((holdprogram: any[]) => {
console.log(holdprogram);
console.log(holdprogram.office.id);
console.log(holdprogram.office.name);
console.log(holdprogram.office.shortname);
}, error => {
console.error(error);
},
() => {
});
}
The usual simplest way to keep a reference to a variable obtained via a request is to use a component variable :
in the component :
public export class MyComponent {
...
public office: any; // instead of using 'any', you could create an interface corresponding to the structure
...
}
in the subscribe :
.subscribe((holdprogram: any[]) => {
this.office = holdprogram.office;
console.log(this.office);
// now this.office keeps a reference of your nested variable 'office'.
},
If you need to keep a reference for it across components, it's a bit more comlicated : you could do something similar at the service level (using tap and a local variable), and you'll need to add some more "cache handling" mechanism.

How can I assert that data represented by two JSON strings is equal?

For the first time I'm trying to use unit tests for my projects, but I'm blocked with a test asserting that a model is properly stored.
This is the API controller I want to test:
public function store(QuestionFormRequest $request)
{
$questionRequest = $request->question();
$question = new Question($questionRequest);
$question->save();
$question->answers()->createMany($questionRequest['answers']);
return response()->json($question->load('answers'), 201);
}
This is my test:
public function it_can_store_a_question()
{
$surveyFactory = factory(Survey::class)->create();
$themeFactory = factory(Theme::class)->create();
$pillarFactory = factory(Pillar::class)->create();
$questionToStore = [
'survey_id' => $surveyFactory->id,
'theme_id' => $themeFactory->id,
'pillar_id' => $pillarFactory->id,
'name' => 'question',
'type' => 'simple',
'answers' => [
[
'label' => 'reponse1',
'points' => '3',
],
[
'label' => 'reponse2',
'points' => '5',
]
]
];
$response = $this->post('/api/1.0/question', $questionToStore);
$response->assertStatus(201);
$expectedQuestion = Question::with('answers')->get()->first();
$this->assertEquals(json_encode($expectedQuestion), $response->getContent());
}
This is the result:
Failed asserting that two strings are equal.
Expected :'{"id":1,"survey_id":1,"theme_id":1,"pillar_id":1,"name":"question","type":"simple","created_at":"2017-08-29 08:54:45","updated_at":"2017-08-29 08:54:45","deleted_at":null,"answers":[{"id":1,"question_id":1,"label":"reponse1","points":3,"description":"","created_at":"2017-08-29 08:54:45","updated_at":"2017-08-29 08:54:45","deleted_at":null},{"id":2,"question_id":1,"label":"reponse2","points":5,"description":"","created_at":"2017-08-29 08:54:45","updated_at":"2017-08-29 08:54:45","deleted_at":null}]}'
Actual :'{"survey_id":1,"theme_id":1,"pillar_id":1,"name":"question","type":"simple","updated_at":"2017-08-29 08:54:45","created_at":"2017-08-29 08:54:45","id":1,"answers":[{"id":1,"question_id":1,"label":"reponse1","points":3,"description":"","created_at":"2017-08-29 08:54:45","updated_at":"2017-08-29 08:54:45","deleted_at":null},{"id":2,"question_id":1,"label":"reponse2","points":5,"description":"","created_at":"2017-08-29 08:54:45","updated_at":"2017-08-29 08:54:45","deleted_at":null}]}'
In fact, the result is right. But not in the same order.
What am I doing wrong in my test?
Thanks.
For your example you could use assertions shipped with phpunit/phpunit:
assertJsonFileEqualsJsonFile()
assertJsonStringEqualsJsonString()
assertJsonStringEqualsJsonFile()
or
assertEquals()
assertJsonFileEqualsJsonFile()
However, when I - for the sake of an example - extract the two JSON strings into separate files
expected.json
actual.json
and then format the JSON in PhpStorm with option + command + L and run the following test
use PHPUnit\Framework;
final class JsonTest extends Framework\TestCase
{
public function testJsonEquals()
{
$expected = __DIR__ . '/expected.json';
$actual = __DIR__ . '/actual.json';
$this->assertJsonFileEqualsJsonFile($expected, $actual);
}
}
the test fails with
Failed asserting that '{\n
"id": 1,\n
"survey_id": 1,\n
"theme_id": 1,\n
"pillar_id": 1,\n
"name": "question",\n
"type": "simple",\n
"created_at": "2017-08-29 08:54:45",\n
"updated_at": "2017-08-29 08:54:45",\n
"deleted_at": null,\n
"answers": [\n
{\n
"id": 1,\n
"question_id": 1,\n
"label": "reponse1",\n
"points": 3,\n
"description": "",\n
"created_at": "2017-08-29 08:54:45",\n
"updated_at": "2017-08-29 08:54:45",\n
"deleted_at": null\n
},\n
{\n
"id": 2,\n
"question_id": 1,\n
"label": "reponse2",\n
"points": 5,\n
"description": "",\n
"created_at": "2017-08-29 08:54:45",\n
"updated_at": "2017-08-29 08:54:45",\n
"deleted_at": null\n
}\n
]\n
}\n
' matches JSON string "{
"survey_id": 1,
"theme_id": 1,
"pillar_id": 1,
"name": "question",
"type": "simple",
"updated_at": "2017-08-29 08:54:45",
"created_at": "2017-08-29 08:54:45",
"id": 1,
"answers": [
{
"id": 1,
"question_id": 1,
"label": "reponse1",
"points": 3,
"description": "",
"created_at": "2017-08-29 08:54:45",
"updated_at": "2017-08-29 08:54:45",
"deleted_at": null
},
{
"id": 2,
"question_id": 1,
"label": "reponse2",
"points": 5,
"description": "",
"created_at": "2017-08-29 08:54:45",
"updated_at": "2017-08-29 08:54:45",
"deleted_at": null
}
]
}
".
--- Expected
+++ Actual
## ##
{
- "id": 1,
"survey_id": 1,
"theme_id": 1,
"pillar_id": 1,
"name": "question",
"type": "simple",
+ "updated_at": "2017-08-29 08:54:45",
"created_at": "2017-08-29 08:54:45",
- "updated_at": "2017-08-29 08:54:45",
- "deleted_at": null,
+ "id": 1,
What you can see is that in fact the two JSON strings aren't equal, at least the order of the fields isn't the same. The problem with the output of the assertion is that it's not really helpful, simply because the assertion just compares the two strings.
assertEquals()
Nonetheless, when we json_decode() the content of the files and then assert using assertEquals() like this:
use PHPUnit\Framework;
final class JsonTest extends Framework\TestCase
{
public function testJsonEquals()
{
$expected = json_decode(file_get_contents(__DIR__ . '/expected.json'), true);
$actual = json_decode(file_get_contents(__DIR__ . '/actual.json'), true);
$this->assertEquals($expected, $actual);
}
}
then the test fails again, but with output that is actually much more helpful:
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
## ##
- 'deleted_at' => null
As you can see, there's an actual difference between the data - the root object node is missing a deleted_at field with a value of null.
Note So, the question boils down to
do you want to assert that two JSON strings are equal or
do you want to assert that the data represented by two JSON strings are equal
Depending on which is important to you, pick whichever assertion makes more sense to you.
The JSON output is just a little bit different - instead of comparing the actual string output you'd be better to use one of Laravel's JSON assertions to ensure the output contains what you expect. Take a look at the available assertions in the documentation. You might use $response->assertJson() or $response->assertJsonFragment() to ensure the required items are returned.

How to set aliases and then still affect the return type for queries in json formats for Cake 3.x?

Situation
Using cake 3.2.4
and the plugin Crud 4.2
for the purpose of producing an API feed
What code did I use at first?
$this->Crud->on('beforePaginate', function(Event $event) use ($conditions) {
$this->paginate['conditions'] = $conditions;
$this->paginate['limit'] = 100;
$this->paginate['fields'] = [
'id', 'title',
'start_date',
'end_date',
'revenue',
'total_costs', 'collections'
];
});
What am I getting as json feed?
"success": true,
"data": [
{
"id": 789,
"title": "9143 - Asia Summit",
"start_date": "2016-03-02T09:00:00+0800",
"end_date": "2016-03-04T18:45:00+0800",
"revenue": 1000000.00,
"total_costs": 0,
"collections": 50000.00
},
{
"id": 15,
"title": "9144 - 10th Exhibition",
"start_date": "2016-03-21T09:00:00+0800",
"end_date": "2016-03-23T17:00:00+0800",
"revenue": 2000000.00,
"total_costs": 0,
"collections": 50000.00
}]}
What did I then do?
I wanted to return as aliases so I did the following
$this->Crud->on('beforePaginate', function(Event $event) use ($conditions) {
$this->paginate['conditions'] = $conditions;
$this->paginate['limit'] = 100;
$this->paginate['fields'] = [
'id', 'title',
'start' => 'start_date',
'end' => 'end_date',
'revenue',
'costs' => 'total_costs', 'collections'
];
});
What did I now get?
{
"success": true,
"data": [
{
"id": 789,
"title": "9143 - Asia Summit",
"start": "2016-03-02 09:00:00",
"end": "2016-03-04 18:45:00",
"revenue": 1000000.00,
"costs": "0.00",
"collections": 50000.00
},
{
"id": 15,
"title": "9144 - 10th Exhibition",
"start": "2016-03-21 09:00:00",
"end": "2016-03-23 17:00:00",
"revenue": 2000000.00,
"costs": "0.00",
"collections": 50000.00
},
So what's wrong?
I get the aliases I wanted, but then the costs has now become a string.
The datetime no longer shows the timezone.
How do I force the fields of the objects in the json feed to be of a certain type whilst keeping the aliases?
Realise that in 3.2 you can use addDefaultTypes to affect the query directly
See Cakephp-3.x: How to change the data type of a selected alias?
Realise that you can access the query from $event when using Crud
See http://crud-view.readthedocs.org/en/latest/basic-usage.html?highlight=query#providing-associations-to-be-displayed
Use the types datetime, and decimal
Put it altogther and you get:
$this->Crud->on('beforePaginate', function(Event $event) use ($conditions) {
$this->paginate['conditions'] = $conditions;
$this->paginate['limit'] = 100;
$this->paginate['fields'] = [
'id', 'title',
'start' => 'start_date',
'end' => 'end_date',
'revenue',
'costs' => 'total_costs', 'collections'
];
$paginationQuery = $event->subject()->query;
$paginationQuery
->selectTypeMap()
->addDefaults([
'start' => 'datetime',
'end' => 'datetime',
'costs' => 'decimal'
]);
});