Best practice for client to choose response format (JSON / XML)

Asked

Viewed 307 times

6

I would like to know the best coding practice that allows the client to define the response format for the request he made, which can also include filters, conditions, ordering, etc...

I made a small model for the answer, I do not know if it is the best practice, but it works (advice are welcome). I coded in Middleware after();

PS.: The format choice will be dynamic after constructing the request code.

Now about the request. I imagined coding in Middleware before(). What is the best way to do?

Follows the code:

index php.

<?php

define('ROOT', dirname(__DIR__));
chdir(ROOT);

require 'vendor/autoload.php';
require 'src/Config/bootstrap.php';
require 'src/Config/routes.php';

$app->run();

bootstrap.php

<?php

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$app = new Application();

$app['serializer'] = function(){
    $encoders = array(new XmlEncoder(), new JsonEncoder());
    $normalizers = array(new ObjectNormalizer());
    return new Serializer($normalizers, $encoders);
};

$app['debug'] = true;

/*$app->before(function (Request $request) use ($app){
  $request->query->
});*/

$app->after(function (Request $request, Response $response) use ($app){
  //var_dump($response);
  $response->headers->set('Content-Type', 'application/xml');
  return $response;
});

return $app;

Routes.php

<?php

$app->mount('/classificados', require 'src/App/Controllers/ClassificadosController.php');

Classifiedsontroller.php

<?php

use Symfony\Component\HttpFoundation\Response;

$classificados = $app['controllers_factory'];

$classificados->get('/', function() use ($app) {
    $post = array(
        'title' => 'Titulo',
        'body'  => 'corpo',
    );

    $serializeContent = $app['serializer']->serialize($post, 'xml');
    return new Response($serializeContent, 200);
});

return $classificados;

How best to build a logic to streamline the response format (json or xml) for the client?

UPDATE

I refactored my code by the @Guilherme Nascimento response, had the idea of always returning a json from Controller and in after(), if an xml has been asked to deserialize the return and serialize in a new Answer in xml format, if a json is requested it would return the Sponse itself without performing this procedure, with the intention of abstracting this procedure from each route? Does it get too expensive for the server?

Stayed like this:

bootstrap.php

<?php

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$app = new Application();
$app['debug'] = true;

$app['formatSerialize'] = function($format){
  $app['formatSerialize'] = $format;
};

$app->before(function (Request $request) use ($app) {
  $app['formatSerialize'] = (array_key_exists('xml', $request->query->all()) == 1) ? 'xml' : 'json';
});

$app->after(function (Request $request, Response $response) use ($app){
  if ($app['formatSerialize'] == 'xml'){
    $serializer = new Serializer(array(new ObjectNormalizer()), array(new XmlEncoder(), new JsonEncoder()));

    $data = json_decode($response->getContent(), true);
    $serializeContent = $serializer->serialize($data, $app['formatSerialize']);

    $resp = new Response($serializeContent, $response->getStatusCode());
    $resp->headers->set('Content-Type', 'application/xml');
    return $resp;
  }
  $response->headers->set('Content-Type', 'application/json');
  return $response;
});

return $app;

Classifiedsontroller.php

<?php

$classificados = $app['controllers_factory'];

$classificados->get('/', function() use ($app) {
    $post = array(
        'title' => 'Titulo',
        'body'  => 'corpo',
    );

    return $app->json($post, 200);
});

return $classificados;

This deserialization and re-serialization is very costly?

  • A good practice is to let and return type being informed by the application header. In the case who is making the request, must inform in the header Content-Type application/json or application/xml

2 answers

4


Best practice I think "has not", depends a lot on the comfort of what you want to pass to the customer, however I recommend trying some of these

  1. You can set a route for each:

    $classificados->get('/xml', function() use ($app) {
    //...Coloque a execução para serializer como Xml aqui
    

    Json:

    $classificados->get('/json', function() use ($app) {
    //...Coloque a execução para serializer como Json aqui
    
  2. Or you can use the Accept: header, in this case it will depend on the client side software to send the header Accept along with the request, for example Ajax, or a mobile application, for direct access via browser will not work.

    To use just your Ajax (if you use something like this), do this:

    xhr.open("GET", url, true);    // async
    xhr.setRequestHeader("Accept", "text/xml"); //Requisita XML
    xhr.onreadystatechange = OnStateChange;
    xhr.send(null);
    

    If it’s a Mobile application that uses Java or C++ (Android) or Object-c or Switft (iOS/Mac) it depends on how you do it, but it’s not hard to find out, and in php you should do so:

    //$request se refere a classe `Symfony\Component\HttpFoundation\Request`
    $contentType = $request->headers->get('Accept');
    
    if (strpos($contentType, 'application/json') === 0) {
        //Serialize Json
    } else if (strpos($contentType, '/xml') !== false) {
        //Serialize Xml
    }
    
  3. Or you can even solve with a simple use of GET, something like http://site/?type=json or http://site/?type=xml:

    switch ($request->query->get('type')) {
        case 'json':
            //Serialize Json
        break;
        case 'xml':
            //Serialize Xml
        break;
    }
    
  4. Or you can even create a route that simulates a file:

    //Acesse via http://site/api.xml
    $classificados->get('/api.xml', function() use ($app) {
        //...Coloque a execução para serializer como Xml aqui
    

    Json:

    //Acesse via http://site/api.json
    $classificados->get('/api.json', function() use ($app) {
       //...Coloque a execução para serializer como Json aqui
    

    Or you can also use something like:

    //Acesse via http://site/api.json ou http://site/api.xml
    $app->get('/api.{format}', function($format) use ($app) {
        switch ($format) {
            case 'json':
                //Serialize Json
            break;
            case 'xml':
                //Serialize Xml
            break;
        }
    }
    
  • Thanks for the @Guilherme reply. I don’t know if I understand Option 2, I’ll get which "Content-Type" was requested by the customer? How he, the Client, would report this?

  • @Leofelipe I typed wrong, I will correct

  • 1

    I understood now, for me this form, of Option 2, would be the most "beautiful" of coding. While I am making simple examples and testing right in the browser, I will use Option 3, but for future projects, I will use Option 2 anyway. I will update as I re-read my code. Thank you very much.

  • @Leofelipe I also like option 2 very much, but still I would prefer option 1 or 3, chance to implement Cache system, because so avoid conflicts ;)

  • Hmm.... I get it, good guy. come here, I forgot to ask. Do you think the process of deserialization and soon after a new serialization is too costly pro server? With the intention of abstracting this in the Controller, I implemented this process. I put the Update.

  • But because you need to serialize and deserialisate on the same call?

  • Only to remove this responsibility ( from 1 line ) to the return of each route. So every route would always return a json, if the requested format was json it would not do the deserialization and reserialization, but if what was requested was an xml, then this process would occur, in order to transform it into xml. I just don’t know how expensive it would be with big data, because there’s no way I can test it here.

  • I think it’s very confusing why to do everything in the same request, I’ll take a closer look at Silex and try to understand the need for before and after, as soon as possible I’ll be back.

  • To make more intuitive the solution 3, could not pass the information as extension of the same URL? /site/classificados.json or /site/classificados.xml?

  • @Andersoncarloswoss ready, thanks for the tip ;)

Show 5 more comments

2

According to the Documentation for Silex 2, you can use the method Silex\Application::view to set the data presentation configuration for the user.

$app->view(function (array $controllerResult, Request $request) use ($app) {

    $acceptHeader = $request->headers->get('Accept');

    $bestFormat = $app['negotiator']->getBestFormat($acceptHeader, array('json', 'xml'));

    if ('json' === $bestFormat) {
        return new JsonResponse($controllerResult);
    }

    if ('xml' === $bestFormat) {
        return $app['serializer.xml']->renderResponse($controllerResult);
    }

    return $controllerResult;
});

This view method is a response provider. It will cause the response to be given to the user according to the parameter Accept present in headers, but you can customize to do this via GET parameter.

I believe that the way you did it is not wrong, but since there is a specific method to deal only with the presentation, as shown above, I would choose to use it.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.