Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

JSON-LD: Contexto para tus datos

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Una API típica devuelve JSON. Entra en /api/cheese_listings/2.json. Cuando pienso en una API, esto es lo que tradicionalmente imagino en mi cabeza.

Tus datos carecen de significado

Pero, ¿qué significado tienen estos campos? ¿Qué significan exactamente los campos titleo description? ¿Son texto plano? ¿Pueden contener HTML? ¿La descripción describe este tipo de queso en general, o es específica del estado del queso exacto que estoy vendiendo? ¿Y el precio? ¿Es una cadena, un flotador, un número entero? ¿Está en dólares estadounidenses? ¿En euros? ¿Se mide en céntimos?

Si eres un humano... eres un humano, ¿verdad? Un humano suele poder "inferir" algún significado a partir de los nombres de los campos o encontrar alguna documentación legible por humanos que le ayude a saber exactamente lo que representa cada campo. Pero no hay forma de que una máquina entienda nada de lo que significan estos campos o sus tipos. ¡Incluso un algoritmo inteligente podría confundirse! Un campo llamado title podría ser el "título" de algo -como el título de un libro- o podría ser el título de una persona -Sr., Sra., etc.

RDF Y HTML

Esto es lo que pretende resolver JSON-LD. De acuerdo, honestamente, hay mucho que hacer en estos días con este problema de:

¿Cómo damos a los datos en la web un contexto o significado que los ordenadores puedan entender?

Así que vamos a tocar algunos puntos básicos. Existe una cosa llamada RDF (Resource Description Framework), que es una especie de conjunto de reglas sobre cómo podemos "describir" el significado de los datos. Es un poco abstracto, pero es una guía sobre cómo puedes decir que un dato tiene este "tipo" o que un recurso es una "subclase" de algún otro "tipo". En HTML, puedes añadir atributos a tus elementos para añadir metadatos RDF, diciendo que algún div describe a una Persona y que los name y telephone de esta Persona son estos otros datos:

<p typeof="http://schema.org/Person">
   My name is
   <span property="http://schema.org/Person#name">Manu Sporny</span>
   and you can give me a ring via
   <span property="http://schema.org/Person#telephone">1-800-555-0199</span>.
</p>

<!-- or equivalent using vocab -->
<p vocab="http://schema.org/" typeof="Person">
   My name is
   <span property="name">Manu Sporny</span>
   and you can give me a ring via
   <span property="telephone">1-800-555-0199</span>.
</p>

Esto hace que tu HTML no estructurado sea comprensible para las máquinas. Es aún más comprensible si 2 sitios diferentes utilizan exactamente la misma definición de "Persona", por lo que los "tipos" son URLs y los sitios intentan reutilizar los tipos existentes en lugar de inventar otros nuevos.

¡Es genial!

Hola JSON-LD

JSON-LD nos permite hacer esto mismo para JSON. Cambia la URL de .jsona .jsonld. Esto tiene los mismos datos, pero con unos cuantos campos extra:@context, @id y @type. JSON-LD no es más que un "estándar" que describe unos cuantos campos extra que puede tener tu JSON -todos ellos empiezan por @ - que ayudan a las máquinas a saber más sobre tu API.

JSON-LD: @id

Así que, primero: @id. En una API RESTful, cada URL representa un recurso y debe tener su propio identificador único. JSON-LD hace esto oficial diciendo que cada recurso debe tener un campo @id... lo que puede parecer redundante ahora mismo... porque... también estamos emitiendo nuestro propio campo id. Pero hay dos cosas especiales sobre @id. En primer lugar, cualquier persona, o cualquier cliente HTTP, que entienda JSON-LD sabrá buscar @id. Es la "clave" oficial del identificador único. Nuestra columna id es algo específico de nuestra API. En segundo lugar, en JSON-LD, todo se hace con URLs. Decir que el id es 2 está bien... ¡pero decir que el id es/api/cheese_listing/2 es infinitamente más útil! ¡Es una URL que alguien podría utilizar para obtener detalles sobre este recurso! También es única dentro de toda nuestra API... o realmente... si incluyes nuestro nombre de dominio, ¡es un identificador único para ese recurso en toda la web!

Esta URL se llama en realidad un IRI: Identificador de Recursos Internacionalizado. Vamos a utilizar los IRI en todas partes en lugar de los ids enteros.

JSON-LD @contexto y @tipo

Las otras dos claves JSON-LD - @context y @type - funcionan juntas. La idea es realmente genial: si añadimos una clave @type a cada recurso y luego definimos los campos exactos de ese tipo en alguna parte, eso nos da dos superpoderes. En primer lugar, sabemos al instante si dos estructuras JSON diferentes describen en realidad un listado de quesos... o si sólo se parecen y en realidad describen cosas diferentes. Y en segundo lugar, podemos mirar la definición de este tipo para saber más sobre él: qué propiedades tiene e incluso el tipo de cada propiedad.

Caramba, ¡esto no es nada nuevo! ¡Lo hacemos todo el tiempo en PHP! Cuando creamos una clase en lugar de una simple matriz, estamos dando a nuestros datos un "tipo". Esto nos permite saber exactamente con qué tipo de datos estamos tratando y podemos mirar la clase para saber más sobre sus propiedades. Así que... sí, el campo @type transforma los datos de una matriz sin estructura en una clase concreta que podemos entender

Pero... ¿dónde se define este tipo CheeseListing? Ahí es donde entra @context: básicamente dice:

Para obtener más detalles, o "contexto" sobre los campos utilizados en estos datos, ve a esta otra URL.

Para que esto tenga sentido, tenemos que pensar como una máquina: una máquina que quiere desesperadamente aprender todo lo posible sobre nuestra API, sus campos y lo que significan. Cuando una máquina ve ese @context, lo sigue. Sí, pongamos literalmente esa URL en el navegador: /api/contexts/CheeseListing. Y... interesante. Es otro@context. Sin entrar en demasiados detalles locos, @context nos permite utilizar nombres de propiedades "abreviados", llamados "términos". Nuestra respuesta JSON real incluye campos como title y description. Pero en lo que respecta a JSON-LD, cuando se tiene en cuenta el @context, es como si la respuesta tuviera un aspecto similar al siguiente:

{
    "@context": {
        "@vocab": "https://localhost:8000/api/docs.jsonld#"
    },
    "@id": "/api/cheese_listing/2",
    "@type": "CheeseListing",
    "CheeseListing/title": "Giant block of cheddar cheese",
    "CheeseListing/description": "mmmmmm",
    "CheeseListing/price": 1000,
}

La idea es que sabemos que, en general, este recurso es del tipo CheeseListing, y cuando encontremos su documentación, deberíamos encontrar información también sobre el significado y los tipos de las propiedades CheeseListing/title o CheeseListing/price. ¿Dónde está esa documentación? Sigue el enlace @vocab a /api/docs.jsonld.

Es una descripción completa de nuestra API en JSON-LD. Y, compruébalo. ¡Tiene una sección llamada supportedClasses, con una clase CheeseListing y todas las diferentes propiedades debajo de ella! Así es como una máquina puede entender lo que significa la propiedadCheeseListing/title: tiene una etiqueta, detalles sobre si es o no necesaria, si es o no legible y si es o no escribible. Para CheeseListing/price, ya sabe que se trata de un número entero.

¡Esta es una información poderosa para una máquina! Y si estás pensando

¡Un momento! ¿No es ésta exactamente la misma información que nos daba la especificación OpenAPI?

Pues no te equivocas. Pero hablaremos de ello dentro de un rato.

En cualquier caso, lo más interesante es que la Plataforma API obtiene todos los datos sobre nuestra clase y sus propiedades de nuestro código Por ejemplo, mira la propiedadCheeseListing/price: tiene un título, el tipo de xmls:integery algunos datos.

Por cierto, incluso ese tipo xmls:integer proviene de otro documento. No lo he mostrado, pero al principio de esta página, hacemos referencia a otro documento que define más tipos, incluido lo que significa el "tipo" xmls:integer en un formato legible por la máquina.

De todos modos, de vuelta a nuestro código, por encima del precio, añade algo de phpdoc:

El precio de este delicioso queso en céntimos.

Actualiza ahora nuestro documento JSON-LD. ¡Bum! ¡De repente tenemos un campo hydra:description! A continuación hablaremos de lo que es "Hydra".

Cómo se ve esto para una máquina

Lo sé, lo sé, todo esto es un poco confuso, bueno, al menos para mí. Pero, intenta imaginarte cómo se ve esto para una máquina. Vuelve al JSON original: decía @type: "CheeseListing". Al "seguir" la URL @context, y luego seguir@vocab -casi de la misma manera que seguimos los enlaces dentro de un navegador-, ¡podemos acabar encontrando detalles sobre lo que realmente significa ese "tipo"! Y haciendo referencia a documentos externos en @context, podemos, en cierto modo, "importar" más tipos. Cuando una máquina ve xmls:integer, sabe que puede seguir este enlace xmlspara saber más sobre ese tipo. Y si todas las APIs utilizaran este mismo identificador para los tipos enteros, bueno, de repente, las APIs serían súper comprensibles para las máquinas.

De todos modos, no es necesario que puedas leer estos documentos y que tengan un sentido perfecto. Mientras entiendas lo que todo esto de los "datos enlazados" y los "tipos" compartidos intentan conseguir, estarás bien.

Vale, ya casi hemos terminado con todo este rollo teórico, lo prometo. Pero antes, tenemos que hablar de lo que es "Hydra", y ver algunas otras entradas geniales que ya están en hydra:supportedClass.

Leave a comment!

19
Login or Register to join the conversation
hanen Avatar

Hi , I got an error When I add .json or .jsonld to the URL: 'The requested resource /api/cheese_listings/1.json was not found on this server.'

3 Reply

Hey hanen

Can you double check that there is a CheeseListing record with an ID set to 1 on your database?

Cheers!

Reply
hanen Avatar

I use the curl command curl -X GET "http://localhost:8000/api/cheese_listings" -H "accept: application/ld+json"

{"@context":"\/api\/contexts\/CheeseListing","@id":"\/api\/cheese_listings","@type":"hydra:Collection","hydra:member":[{"@id":"\/api\/cheese_listings\/1","@type":"CheeseListing","id":1,"title":"eating blue cheese","description":"still... good","price":100,"createdAt":"2019-07-23T10:06:53+02:00","isPublished":true},

When Im using api/cheese_listings/1 I have the api interface with all availables resources but entrypoint like /api/cheese_listings/1.jsonld return 404 not found

Reply

Hey hanen!

Hmm, it could just be a little web server problem. What web server are you using? When some web-servers see a "." (e.g. 1.json) - they assume you're trying to access a physical file. And so, instead of executing the framework like normal, they 404 when they can't find that file. One fix / way to confirm this is to see if going to "/index.php/api/cheese_listing/1.json" works :).

Cheers!

1 Reply
hanen Avatar
hanen Avatar hanen | weaverryan | posted hace 3 años | edited

oh awesome !! it works weaverryan thanks a lot :)))

Reply
Yves Avatar

Hi and many thanks for the informative and entertaining course! :) When I call entities via "/api/contexts/", e.g. "/api/contexts/CheeseListing", all attributes resp. getter/setters of the entity class are exposed. Is it possible to avoid this? Unfortunately, it is not enough to restrict an API resource via normalization and denormalization contexts. Because of the principle of information hiding I only want to expose those fields under "/api/contexts/" that can be used for the API operations. Thanks a lot! :)

Reply

Hey Yves

I recommend you to read about Serialization groups or you want watch this chapter https://symfonycasts.com/sc...
Basically, you define property by property if you want to make it readable and/or writeable

Cheers!

Reply
Yves Avatar

I've already looked for a solution on api-platform.com, but found nothing ...

Reply
Yves Avatar

Hi Diego, thanks for you answer! :) I've already tried serialization groups. This works very well for the read and write schemas of the API operations. There I only see what I annotate. What I was asking for is the type definition of "CheeseListing" under the @context path "/api/contexts/CheeseListing". In the example of this course, "createdAt" and "isPublished" are also visible there, although these methods don't have a serialization group in the entity class "CheeseListing.php". My question is: how can I completely avoid the publication of these class methods in the @context type documentation? They are not relevant for the API usage and reveal more about the internal implementation than you might want to reveal.

Reply

Ohh I wasn't aware of that. Hmm, maybe this chapter can help you https://symfonycasts.com/sc...
Ryan shows how you can modify the metadata of your endpoints, it requires some work but I think that's what you need

Cheers!

Reply
Gustavo C. Avatar
Gustavo C. Avatar Gustavo C. | posted hace 3 años

hi , there are framework with creates entities from web api external ?
How does doctrine do with mapping databases to entities :)

Reply

Hey Gustavo C.

I don't fully understand what you mean with "framework with creates entities from web api external". But what Doctrine does, in short, is to read some metadata from your entities that you define/write, and then convert it into SQL statements and execute them

Cheers!

Reply
Gustavo C. Avatar

Diego , como va , lo que digo si hay alguna funcion de symfony que pueda desde una REST API externa tomar todos los metodos (put , get , delete ..... ,etc) y volcarlos en entidades ? lo de doctrine lo digo ya que hay una funcion que permite( a la inversa de lo que me explicas ) de la base de datos obtener las entidades ya con todos sus metodos.

Reply

Hola Gustavo. La verdad desconosco si existe alguna libreria que te pueda ayudar a hacer lo que necesitas, a la mejor si dicha API soporta el formato json+ld, de esa forma podrias extraer/leer toda la metadata de los llamados y generar las entidades pero es algo que no he hecho antes. Si descubres algo interesante al respecto, escribenos!

Saludos!

1 Reply
Gustavo C. Avatar

Gracias Diego :)

Reply
Ronald V. Avatar
Ronald V. Avatar Ronald V. | posted hace 3 años

What google chrome extension you added to display the JSON properly?

Reply

Hey Ronald V.

I'm not 100% sure of Ryan's extension but I'm using "Json Formatter" and it's really good. https://github.com/callumlo...

Cheers!

Reply
Souleyman Avatar
Souleyman Avatar Souleyman | posted hace 3 años

For me the link /api/context/CheeseListing goes back No route found for "GET /api/context/CheeseListing", it is rather this link /api/contexts/CheeseListing that works for me!
!!

Reply

Hey Souleyman

You are 100% right! Thanks for informing us - I already fixed it :)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

Este tutorial funciona muy bien para Symfony 5 y la Plataforma API 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9
    }
}