Datos nutricionales
Noms guarda un número por nutriente, por cada 100 del alimento: por 100 gramos si es un sólido, por 100 mililitros si es una bebida. Aparte, te dice los tamaños en que la gente mide ese alimento. Cada cifra que muestras en pantalla es la multiplicación de esas dos cosas.
Esta página es esa multiplicación, de punta a punta.
La idea, en palabras simples
Digamos que pides una barrita. Vienen tres cosas con ella:
basis_unit grams → cada valor de abajo es por 100 g
PROTEIN 10 g → 10 g de proteína en cada 100 g
una porción 1 barrita, 50 g → una porción son 50 g
Léelo como una sola frase:
Hay 10 g de proteína en cada 100 g, y una barrita son 50 g.
Una barrita es la mitad de 100 g, así que tiene la mitad de la proteína: 5 g. Dos barritas, 10 g. Tres, 15 g. Media barrita, 2,5 g.
No existe un campo de "proteína por barrita" que buscar, y no hace falta que exista. Escalas el valor por 100 según la porción que te interese, y esa misma multiplicación te da cada nutriente en cualquier tamaño de porción. Así es como un puñado de números guardados reconstruye cualquier etiqueta nutricional que quieras imprimir.
La fórmula
La fórmula
cantidad en una porción = nutriente.value × porción[alimento.basis_unit] / 100
basis_unit hace dos trabajos a la vez, y es el campo que más conviene mirar con
calma. Te dice por cuánto están expresados los valores (grams o milliliters)
y además nombra el campo de la porción por el que hay que dividir:
basis_unit | Los valores son por | Divide por el campo |
|---|---|---|
"grams" | 100 g | grams de la porción |
"milliliters" | 100 ml | milliliters de la porción |
El campo que nombra nunca es null, así que esa búsqueda siempre resuelve.
Un sólido, en JSON
La misma barrita, tal como la devuelve la API, recortada aquí a los tres campos de los que trata esta página:
{ "basis_unit": "grams", // cada valor de abajo es por 100 g "nutrients": [ { "id": "ENERGY", "name": "Energy", "value": "400", "unit": "kcal" }, { "id": "PROTEIN", "name": "Protein", "value": "10", "unit": "g" } ], "serving_sizes": [ { "unit": "each", "descriptor": "bar", "quantity": "1", // una barrita... "grams": "50", // ...y una barrita pesa 50 g "milliliters": null, // sin volumen; no lo adivinamos "is_default": true } ] }
basis_unit es "grams", así que divides por los grams de la porción, que son
50:
proteína por barrita = 10 × 50 / 100 = 5 g
energía por barrita = 400 × 50 / 100 = 200 kcal
Una bebida, la misma forma
Para algo medido por volumen el método no cambia en nada: solo cambia qué campo lees:
{ "basis_unit": "milliliters", // cada valor de abajo es por 100 ml "nutrients": [ { "id": "ENERGY", "name": "Energy", "value": "60", "unit": "kcal" }, { "id": "PROTEIN", "name": "Protein", "value": "3", "unit": "g" } ], "serving_sizes": [ { "unit": "ml", "descriptor": null, "quantity": "250", // 250 ml... "grams": null, // ...de peso no declarado; la densidad no la adivinamos "milliliters": "250", // ...que son, claro, 250 ml "is_default": true } ] }
basis_unit es "milliliters", así que divides por los milliliters de la
porción:
proteína por botella = 3 × 250 / 100 = 7,5 g
energía por botella = 60 × 250 / 100 = 150 kcal
Fíjate en que grams es null en esa porción. Convertir un volumen en un peso
requiere la densidad del alimento, y eso no lo adivinamos, así que la dimensión
sobre la que el alimento no está anclado es de mejor esfuerzo y puede faltar.
La que basis_unit nombra nunca falta.
Una sola función para ambos casos
Como la unidad nombra el campo, nunca tienes que ramificar:
function amountPerServing(food, nutrient, serving) { const basis = Number(serving[food.basis_unit]); // "grams" o "milliliters" return (Number(nutrient.value) * basis) / 100; } const serving = food.serving_sizes.find((s) => s.is_default); const protein = food.nutrients.find((n) => n.id === "PROTEIN"); amountPerServing(food, protein, serving); // 5
Cómo leer una porción
serving_sizes es una lista. Cada entrada es una forma en que la gente mide ese
alimento: un peso, un volumen o una pieza contable.
| Campo | Qué es |
|---|---|
unit | La medida: g, ml, cup, tbsp, tsp, L, oz, fl_oz o each |
quantity | Cuántas unit forman esta porción: 1 barrita, 250 ml |
descriptor | Nombra la porción cuando la unidad sola no alcanza (bar, slice, chopped); null si no |
grams | Cuánto pesa esta porción, cuando se sabe |
milliliters | Cuánto mide en volumen esta porción, cuando se sabe |
is_default | La porción principal del alimento. Exactamente una por alimento |
Lee la porción de estos campos, nunca del nombre del alimento. Si vas a mostrar
una sola cifra, muestra la porción con is_default: true: es la porción de
referencia que la propia fuente trata como una porción.
Dos cosas para tener presentes
Los decimales llegan como strings
value, quantity, grams y milliliters son strings JSON, no números,
así que nada se pierde por coma flotante en el camino. Parséalos con un tipo
decimal cuando la aritmética importe (Decimal en Python, BigNumber o similar
en JavaScript) y redondea solo en el momento de mostrar. Los valores reales del
catálogo traen más decimales que las cifras redondas de arriba, y por eso mismo.
Cada nutriente trae además su propia unit (g, mg, kcal, ...), y eso es
independiente del basis_unit del alimento. basis_unit dice por cuánto está
expresado el valor; nutrient.unit dice en qué está medido. Proteína en "10" con
unit: "g" en un alimento con base grams se lee: 10 gramos de proteína por cada
100 gramos de alimento.
Una respuesta completa
Todo lo anterior, en una fila real del catálogo: las almendras tostadas con sal de Blue Diamond, con el arreglo de nutrientes tan largo como realmente es:
curl -G https://api.noms.sh/v1/foods/TIgbNPnzCIjX \ --data-urlencode "include=nutrients,serving_sizes" \ -H "Authorization: Bearer $NOMS_KEY"
Los mismos tres campos, la misma multiplicación: 21.428571 × 28 / 100,
redondeado para mostrar, son los 6 g de proteína impresos en el envase. Y lo
mismo vale para cada otra cifra de su tabla.
Próximos pasos
- Autenticación: obtén una key y envíala en cada petición.
- Consultas y modelo de datos: cómo buscar, filtrar, ordenar y moldear.