I have a json object that I received by making a get API call. I make this call to receive a list of objects. It's a list of post... So I have an array of Post Objects.
Here the output :
{
"total":2,
"data":[
{
"id":2,
"user":{
"id":1,
"username":"sandro.tchikovani"
},
"description":"cool",
"nb_comments":0,
"nb_likes":0,
"date_creation":"2014-04-13T20:07:34-0700"
},
{
"id":1,
"user":{
"id":1,
"username":"sandro.tchikovani",
},
"description":"Premier pooooste #lol",
"nb_comments":0,
"nb_likes":0,
"date_creation":"2014-04-13T15:15:35-0700"
}
]
}
I would like to deserialize the data part...
The problem is that the Serializer in Symfony gives me an error ...
The error that I have :
Class array<Moodress\Bundle\PosteBundle\Entity\Poste> does not exist
How I do deserialize :
$lastPosts = $serializer->deserialize($data['data'], 'array<Moodress\Bundle\PosteBundle\Entity\Poste>', 'json');
How can I deserialze the data array... To have an array of Postes. I want to give to my view .twig an array Poste... I did precise the type when I deserialize... So I can't find what is the problem...
Thanks.
I think the best solution here is to create new PosteResponse class, like this one:
namespace Moodress\Bundle\PosteBundle\Response;
use JMS\Serializer\Annotation\Type;
class PosteResponse
{
/**
* #Type("integer")
*/
private $total;
/**
* #Type("array<Moodress\Bundle\PosteBundle\Entity\Poste>")
*/
private $data;
//getters here
}
and deserialize your response to that class:
$response = $serializer->deserialize(
$json,
'Moodress\Bundle\PosteBundle\Response\PosteResponse',
'json'
);
$posts = $response->getData();
That WILL do the trick, and it doesn't require you to decode and encode your json manually which is riddiculous in my opinion.
Since Symfony Serializer Component 2.8 to deserialize array of objects:
$persons = $serializer->deserialize($data, 'Acme\Person[]', 'json');
https://symfony.com/doc/master/components/serializer.html#handling-arrays
A less than ideal solution that I found was to first decode and then encode the json data again at the node that represents the data array. For example in your case:
$json = json_decode($json);
$json = json_encode($json->data);
$serializer->deserialize($json, 'array<Moodress\Bundle\PosteBundle\Entity\Poste>', 'json');
There must be a better solution than this but this seems more elegant than the above solution of de-serialising json.
The error is pretty clear. Your string does not match any existant class.
The example in official documentation says:
$person = $serializer->deserialize($data,'Acme\Person','xml');
In your case it should be more like:
$person = $serializer->deserialize($data['data'],'Moodress\Bundle\PosteBundle\Entity\Poste','json');
Update:
Ok then.
First, your json file does not seem to be valid (use http://jsonlint.com/ to test it). Be careful of that.
Second, you will have to fetch your json as an array with
$data = json_decode($yourJsonFile, true);
and then you can access to each 'data' array with
foreach($data['data'] as $result)
{
/* Here you can hydrate your object manually like:
$person = new Person();
$person->setId($user['id']);
$person->setDescription($user['description']);
Or you can use a denormalizer. */
}
I would make something like this
class PostsModel
{
/**
* #var int
*/
private $total;
/**
* #var PostModel[]
*/
private $data;
}
class PostModel
{
/**
* #var int
*/
private $id;
/**
* #var UserModel
*/
private $user;
/**
* #var string
*/
private $description;
/**
* #var int
*/
private $nb_comments;
/**
* #var int
*/
private $nb_likes;
/**
* #var \DateTime
*/
private $date_creation;
}
class UserModel
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $username;
}
And in controller
$posts = $this->serializer->deserialize($data, PostsModel::class, 'json');
And this will return $postsModel with $data property which will have your array of entities
In case someone will be searching how to decode an array of objects using Symfony Serializer:
use Moodress\Bundle\PosteBundle\Entity\Poste;
// your json data
$data = '{
"total":2,
"data":[
{...},
{...}
]
}';
$lastPosts = $serializer->deserialize(
$data['data'],
'Poste[]', // or Poste::class.'[]',
'json'
);
Notice that you need to add [] after your class name, in this way Symfony will recognize your json data as an array of objects.
Related
Let's say I have a simple Post entity, with a $author ManyToOne relation :
Class Post {
/**
* #ORM\ManyToOne(targetEntity=Author::class, inversedBy="posts")
* #ORM\JoinColumn(nullable=false)
*/
private $author;
/**
* #ORM\Column(type="string", length=255)
*/
private $title;
...
}
Then a controller function like this :
public function create(Request $request, EntityManagerInterface $em, SerializerInterface $serializer): Response
{
$data = $request->getContent();
$post = $serializer->deserialize($data, Post::class, 'json');
$em->persist($post);
$em->flush();
}
With a JSON format $data looking like this :
{
"author": { "id": 10 },
"title": "My Title",
...
}
Is there a way to make Serializer deserialize the author['id'] to it's entity reference ?
I read that this is what the PropertyInfo component of Symfony is supposed to do when enabled. But even with PropertyInfo enabled, the persist operation is still creating a new Author instead of refering to an existing one (Doctrine generates a new author row instead of setting the author_id of the post row)
Maybe by adding cascade={"persist"} on $author property annotation ?
Or forcing setter `
$post = $serializer->deserialize($data, Post::class, 'json');
$post->setAuthor($em->getRepository(Author::class)->findOneBy([]));
`
I make a REST API on Symfony4, So I would like to Serialize my Entity with the default serializer of Symfony4.
But my entities have unusual attribute names that make serializer give me bad result.
I tried to implement NameConverterInterface and also tried CamelCaseToSnakeCaseNameConverter without a good result...
Every entity on my application have this kind of attribute so a solution with #annotation can't help me
class Product implements EntityInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer", name="PROD_PKEY")
*/
private $PROD_PKEY;
/**
* #ORM\Column(type="string", length=50)
*/
private $PROD_Name;
/**
* #ORM\Column(type="string", length=50)
*/
private $PROD_Code;
And how I use the serializer :
$product = new Product();
$product->setPRODName("Name");
$product->setPRODCode("Code");
$json = $this->serializer->serialize($product, 'json');
The content of $json is :
{
"pRODName": "Name",
"pRODCode": "Code",
}
but I expect somethings like this :
{
"PROD_Name": "Name",
"PROD_Code": "Code",
}
Simply equal to my attributes names in my entity, I don't understand why first letter get lowercase and my underscore get out...
Thanks for your help !
In Symfony you can implement a custom NameConverter to transform fields names in your json representation.
Something along these lines should do the trick:
<?php namespace App\Service;
use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
class YourCustomNameConverter implements AdvancedNameConverterInterface
{
public function normalize($propertyName, string $class = null, string $format = null, array $context = [])
{
preg_match('/^([a-z]?[A-Z]+)([A-Z]{1}[_a-zA-Z]+)$/', $propertyName, $matches);
if (strstr($propertyName, 'PKEY')) {
return ucfirst(substr_replace($propertyName, '_', -4, 0));
} elseif (count($matches)) {
array_shift($matches);
$matches[0] = ucfirst($matches[0]);
return implode('_', $matches);
} else {
return $propertyName;
}
}
public function denormalize($propertyName, string $class = null, string $format = null, array $context = [])
{
return $propertyName;
}
}
I think you might need to create a custom serializer, I often use jmsserializer bundle and had no issues with it
How to use JMSSerializer with symfony 4.2
https://symfony.com/doc/current/serializer/custom_normalizer.html
When getting an object from an API, I receive a properly serialized Course object.
"startDate": "2018-05-21",
But when I create a new object and try to return it, the formatting is not applied.
"startDate": "2019-02-01T02:37:02+00:00",
Even if I use the repository to get a new Course object, if it is the same object I have just created then it is still not serialized with formatting. Maybe because it is already loaded in memory by that point?
If I use the repository to get a different course from the database then the serialization formatting is applied.
I expected formatting to be applied when I return a Course object regardless of whether it has just been created or not. Any ideas?
Course class
/**
* #ORM\Entity(repositoryClass="App\Repository\CourseRepository")
*
* #HasLifecycleCallbacks
*/
class Course
{
/**
* #var string
*
* #ORM\Column(type="date")
*
* #Assert\Date
* #Assert\NotNull
*
* #JMS\Type("DateTime<'Y-m-d'>")
* #JMS\Groups({"courses-list", "course-details"})
*/
private $startDate;
/**
* #return string
*/
public function getStartDate(): string
{
return $this->startDate;
}
}
Course API Controller Class
public function getCourse($id)
{
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('App:Course');
$course = $repo->find($id);
if(!$course) {
throw new NotFoundHttpException('Course not found', null, 2001);
}
return $course;
}
public function addCourse(Request $request) {
$course = new Course();
$course->setStartDate($startDate);
$validator = $this->get('validator');
$em->persist($course);
$em->flush();
return $course;
}
Turns out that you shouldn't use Carbon objects with JMS Serializer.
As soon as I set DateTime objects on the Course object instead of Carbon objects, it worked fine.
Strange behaviour considering that they both implement DateTimeInterface.
When I deserialize my doctrine entity, the initial object is constructed/initiated correctly, however all child relations are trying to be called as arrays.
The root level object's addChild(ChildEntity $entity) method is being called, but Symfony is throwing an error that addChild is receiving an array and not an instance of ChildEntity.
Does Symfony's own serializer have a way to deserialize nested arrays (child entities) to the entity type?
JMS Serializer handles this by specifying a #Type("ArrayCollection<ChildEntity>") annotation on the property.
I believe the Symfony serializer attempts to be minimal compared to the JMS Serializer, so you might have to implement your own denormalizer for the class. You can see how the section on adding normalizers.
There may be an easier way, but so far with Symfony I am using Discriminator interface annotation and type property for array of Objects. It can also handle multiple types in one array (MongoDB):
namespace App\Model;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
/**
* #DiscriminatorMap(typeProperty="type", mapping={
* "text"="App\Model\BlogContentTextModel",
* "code"="App\Model\BlogContentCodeModel"
* })
*/
interface BlogContentInterface
{
/**
* #return string
*/
public function getType(): string;
}
and parent object will need to define property as interface and get, add, remove methods:
/**
* #var BlogContentInterface[]
*/
protected $contents = [];
/**
* #return BlogContentInterface[]
*/
public function getContents(): array
{
return $this->contents;
}
/**
* #param BlogContentInterface[] $contents
*/
public function setContents($contents): void
{
$this->contents = $contents;
}
/**
* #param BlogContentInterface $content
*/
public function addContent(BlogContentInterface $content): void
{
$this->contents[] = $content;
}
/**
* #param BlogContentInterface $content
*/
public function removeContent(BlogContentInterface $content): void
{
$index = array_search($content, $this->contents);
if ($index !== false) {
unset($this->contents[$index]);
}
}
I'm building a REST API endpint that adds a company to a MySQL database. The client sends a POST request with an attached data package. The data package is a JSON object. Assume the JSON Company Object is formatted to exactly match the Company Class that the API uses.
How do I get the JSON Company Object data into the Company Class? It seems silly to instantiate a Company Object, json_decode() the JSON Object, then call dozens of set() methods.
It seems especially silly, since I'm planning on offering the same models in my client package to build the objects that get passed as JSON to my API, before being decoded, and mapped back into the same objects again.
Am I missing something? I'm constantly running up against things that seem redundant while building my API, but perhaps that's just what has to happen.
Why don't you handle is all in the constructor of the Company object so that you pass in the JSON object as the parameter and the constructor handles all of the assignments. That way you don't even need public set methods.
$companyData = $_POST['company'];
//or $companyData = json_decode($_POST['company']);
//or whatever depending on how you are posting
class Company {
private $companyName;
//etc...
function __construct(array $data) {
foreach($data as $key => $val) {
if(property_exists(__CLASS__,$key)) {
$this->$key = $val;
}
}
}
}
We built JsonMapper to map JSON objects onto our own model classes automatically.
It only relies on docblock type information for mapping, which most class properties have anyway.
Why don't you just make a method in the Company object that declares the variables for the object (you don't need to write a set method for each variable, just one that'll set all the variables).
//Why not write something like this in the class
function setFromJSON($json){
$jsonArray = json_decode($json, true);
foreach($jsonArray as $key=>$value){
$this->$key = $value;
}
}
this one might help you.
class ObjectMapper
{
protected string $modelClass='';
public function __construct(string $modelClass){
$this->modelClass = $modelClass;
}
protected function mapObject(array $values){
$object = new $this->modelClass();
foreach ($values as $key=>$value){
$mapperFunction = 'set'.$key;
$object->{$mapperFunction}($value);
}
return $object;
}
}
Don't match the JSON data you get exactly to the data object you want to use. There will be changes to the database and the objects, and you do not want these to affect the interface you created and vice versa.
I Had the same problem, so I wrote a packagist to solve it, here is the gift for you.
https://packagist.org/packages/stark641/stark-object
https://github.com/stark641/stark-object
you can generate PHP class file from json
./vendor/bin/stark-object-gen --path=/tmp --json='{"foo_bar":{"foo":1.3,"bar":[641,641]}}'
output
Class FooBar saved at: /tmp/FooBar.php
Class BaseClass saved at: /tmp/BaseClass.php
you can map json to php object
class FooBarClass extends StarkObject
{
/** #var string */
public $foo;
/** #var integer */
public $bar;
}
class DemoClass extends StarkObject
{
/** #var FooBarClass */
public $foobar;
/** #var FooBarClass[] */
public $foobars;
}
$json = '{"foobar":{"foo":"hello world","bar":64100},"foobars":[{"foo":"hello","bar":641},{"foo":"world","bar":664411}]}';
$demo = (new DemoClass())->fromJson($json);
var_export($demo);
I think this can help: https://github.com/ABGEO/json-to-popo
I tried it and it works fine!
An example:
We have these php classes
<?php
class Department
{
/**
* #var string
*/
private $title;
// Getters and Setters here...
}
<?php
class Position
{
/**
* #var string
*/
private $title;
/**
* #var Department
*/
private $department;
// Getters and Setters here...
}
<?php
class Person
{
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
private $lastName;
/**
* #var bool
*/
private $active;
/**
* #var Position
*/
private $position;
// Getters and Setters here...
}
Person class is represented by this json:
{
"firstName": "Temuri",
"lastName": "Takalandze",
"active": true,
"position": {
"title": "Developer",
"department": {
"title": "IT"
}
}
}
So, now you only need to do this:
// $jsonContent represents your json string
$resultObject = $composer->composeObject($jsonContent, Person::class);
var_dump($resultObject);
and this is what you get:
//class Person#2 (4) {
// private $firstName =>
// string(6) "Temuri"
// private $lastName =>
// string(10) "Takalandze"
// private $active =>
// bool(true)
// private $position =>
// class Position#4 (2) {
// private $title =>
// string(9) "Developer"
// private $department =>
// class Department#7 (1) {
// private $title =>
// string(2) "IT"
// }
// }
//}
I tried to implement the library with public class properties but it seems it doesn't work without declared setter and getters.