npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jpacked

v1.1.4

Published

A compact data format alternative to JSON, based on schema + CSV

Readme

JPACKED

npm version npm downloads License: MIT

JPACKED es un formato de datos compacto alternativo a JSON, basado en schema + CSV con soporte para metadatos y arrays. Diseñado para reducir el tamaño de las respuestas API y mejorar el rendimiento en transferencias de datos.

📋 Tabla de Contenidos

✨ Características

  • Compacto: Reduce el tamaño de los datos hasta un 40-60% comparado con JSON
  • Tipado: TypeScript completamente tipado
  • Schema-based: Define la estructura una vez, reutiliza en cada fila
  • Metadatos integrados: Soporte nativo para paginación y conteos
  • Arrays eficientes: Codificación optimizada de arrays con delimitadores
  • Objetos anidados: Soporte completo para objetos anidados y estructuras complejas
  • Middlewares listos: Express y Fetch integrados
  • Seguro: Parsing robusto con manejo de escapes y caracteres especiales
  • Optimizado para IA: Reduce tokens en 30-40% comparado con JSON (ideal para ChatGPT, Claude, etc.)

📦 Instalación

npm install jpacked

🚀 Uso Básico

Encoder

import { encode } from 'jpacked';

const data = [
  { id: 1, name: 'Alice', age: 30, tags: ['admin', 'user'] },
  { id: 2, name: 'Bob', age: 25, tags: ['user'] },
];

const metadata = {
  count: 2,
  page: 1,
  pageCount: 5,
  total: 100,
};

const jpackedString = encode(data, metadata);
console.log(jpackedString);

Ejemplo con objetos anidados:

import { encode } from 'jpacked';

const data = [
  {
    id: 1,
    name: 'Alice',
    profile: {
      email: '[email protected]',
      settings: {
        theme: 'dark',
        notifications: true,
      },
    },
    tags: [
      { name: 'admin', level: 5 },
      { name: 'user', level: 3 },
    ],
  },
];

// count se calcula automáticamente desde data.length
const jpackedString = encode(data);

Decoder

Ejemplo básico:

import { decode } from 'jpacked';

const jpackedString = `JPACKED/1.1
meta[1]
schema{id,name,profile{email,settings{theme}},tags[name,level]}
data
1,Alice,[email protected],dark,"admin,5|user,3"`;

const result = decode(jpackedString);
// Los objetos anidados se reconstruyen automáticamente desde campos planos
console.log(result.data[0].profile.email);  // "[email protected]"
console.log(result.data[0].profile.settings.theme);  // "dark"
console.log(result.data[0].tags[0].name);  // "admin"
console.log(result.data[0].tags[0].level);  // 5

📄 Formato JPACKED

Estructura

JPACKED/1.1
meta[COUNT][PAGE?][PAGE_COUNT?][TOTAL?]
schema{field1,field2,object{field3,field4},arrayField[]}
data
value1,value2,value3,value4,array1|array2|array3
value1,value2,value3,value4,array1|array2|array3

Ejemplo Completo

Array de primitivos:

JPACKED/1.1
meta[3][1][2][50]
schema{id,name,email,tags[],active}
data
1,John Doe,[email protected],admin|user,true
2,Jane Smith,[email protected],user,true
3,Bob Wilson,[email protected],guest,false

Nota: tags[] indica que es un array de primitivos (strings en este caso). El [] vacío significa que no hay estructura compleja que definir.

Ejemplo con objetos anidados:

JPACKED/1.1
meta[2]
schema{id,name,profile{email,settings{theme,notifications}},tags[]}
data
1,Alice,[email protected],dark,true,admin|user
2,Bob,[email protected],light,false,user

Reglas de Codificación

Arrays

Tipos de arrays en el schema:

  • tags[] - Array de primitivos (strings, numbers, booleans). El [] vacío indica que no hay estructura compleja.
  • items[name,value] - Array de objetos complejos. El schema define la estructura de cada objeto.

Codificación:

  • Delimitador: |
  • Escape de pipe: \||
  • Escape de backslash: \\\
  • Arrays de primitivos: admin|user|guest
  • Arrays de objetos: "admin,5|user,3" (cada objeto como CSV, separados por |)

Ejemplo - Array de primitivos:

// Valor real: ["a|b", "c\\d"]
// Schema: tags[]
// Codificación: a\|b|c\\d

Ejemplo - Array de objetos:

// Valor real: [{name: "admin", level: 5}, {name: "user", level: 3}]
// Schema: tags[name,level]
// Codificación: "admin,5|user,3"

CSV

  • Comas separan columnas
  • Valores con comas, saltos de línea o comillas se encierran en "..."
  • Comillas internas se duplican: """

Ejemplo:

// Valor: 'He said "Hello"'
// Codificación: "He said ""Hello"""

Arrays en el Schema

Importante: La declaración del array en el schema indica el tipo de contenido:

  • tags[] - Array de primitivos (strings, numbers, booleans). El schema está vacío porque no hay estructura compleja que definir.
  • tags[name,level] - Array de objetos complejos. El schema define la estructura de cada objeto dentro del array.

Ejemplos:

  • tags[]["admin", "user", "guest"] (array de strings)
  • scores[][100, 95, 87] (array de numbers)
  • items[name,value][{name: "item1", value: 100}, {name: "item2", value: 200}] (array de objetos)

Objetos Anidados

  • Las keys de objetos anidados se incluyen en el schema usando formato anidado con llaves
  • El schema usa formato anidado: schema{id,profile{email,settings{theme}}}
  • Los objetos anidados se aplanan internamente para codificar datos (valores planos)
  • Los arrays dentro de objetos se codifican normalmente con |
  • Objetos dentro de arrays: Si todos los elementos del array son objetos, se definen en el schema como tags[name,level] (sin [] antes de las llaves). Cada objeto se codifica como CSV separado por comas, y los objetos se separan con |. Si el array es mixto (contiene objetos y primitivos), los objetos usan formato {field:value}.

Ejemplo:

// Valor real:
{
  id: 1,
  name: 'Alice',
  profile: { 
    email: '[email protected]', 
    settings: { theme: 'dark', notifications: true } 
  },
  tags: ['admin', 'user']
}

// Schema (formato anidado):
schema{id,name,profile{email,settings{theme,notifications}},tags[]}

// Datos codificados (valores planos):
1,Alice,[email protected],dark,true,admin|user

// Nota: Las keys 'email', 'theme', 'notifications' están en el schema,
// NO se repiten en cada fila de datos

Ejemplo con array de objetos:

// Valor real:
{
  id: 1,
  name: 'Alice',
  profile: {
    email: '[email protected]',
    settings: { theme: 'dark' }
  },
  tags: [
    { name: 'admin', level: 5 },
    { name: 'user', level: 3 }
  ]
}

// Schema (formato anidado):
schema{id,name,profile{email,settings{theme}},tags[name,level]}

// Datos codificados (valores planos):
1,Alice,[email protected],dark,"admin,5|user,3"

// Nota: Las keys 'name' y 'level' están en el schema,
// NO se repiten en cada objeto del array. Cada objeto se codifica
// como CSV (valores separados por comas), y los objetos se separan con |

🔧 API

encode(data, metadata?)

Codifica un array de objetos o un objeto único en formato JPACKED. Soporta objetos anidados, arrays de objetos, y estructuras complejas.

Parámetros:

  • data: Record<string, any>[] | Record<string, any> - Array de objetos o un objeto único a codificar (puede incluir objetos anidados)
  • metadata?: EncodeMetadata - Metadatos opcionales (count se calcula automáticamente desde data.length si no se proporciona)

Retorna: string - String JPACKED codificado

Nota: Si pasas un objeto único, se convierte automáticamente en un array de un elemento. El count se calcula automáticamente desde la cantidad de elementos, pero puedes sobrescribirlo si necesitas (por ejemplo, para paginación).

Ejemplo con array:

const data = [
  {
    id: 1,
    name: 'Alice',
    config: {
      enabled: true,
      settings: { theme: 'dark', language: 'es' }
    },
    tags: [{ name: 'admin', value: 1 }]
  }
];

// count se calcula automáticamente (1 en este caso)
const encoded = encode(data);

Ejemplo con objeto único:

const user = {
  id: 1,
  name: 'Alice',
  email: '[email protected]'
};

// Se convierte automáticamente en array de un elemento
// count = 1 automáticamente
const encoded = encode(user);

Ejemplo con metadatos opcionales (paginación):

const data = [{ id: 1 }, { id: 2 }, { id: 3 }]; // 3 elementos

// count se calcula automáticamente como 3
// Pero puedes sobrescribirlo si necesitas (ej: para paginación)
const encoded = encode(data, {
  count: 10, // Total de elementos en la página
  page: 1,
  pageCount: 5,
  total: 50 // Total de elementos en todas las páginas
});

decode<T>(jpackedString)

Decodifica un string JPACKED en objetos.

Parámetros:

  • jpackedString: string - String JPACKED a decodificar

Retorna: DecodeResult<T> - Objeto con data, metadata y schema

Tipos

interface JPACKEDMetadata {
  count: number;        // Siempre presente en resultados decodificados
  page?: number;        // Opcional
  pageCount?: number;   // Opcional
  total?: number;       // Opcional
}

interface EncodeMetadata {
  count?: number;       // Opcional al codificar (se calcula automáticamente desde data.length)
  page?: number;        // Opcional
  pageCount?: number;   // Opcional
  total?: number;       // Opcional
}

interface DecodeResult<T> {
  data: T[];
  metadata: JPACKEDMetadata;
  schema: SchemaField[];
}

🔌 Middlewares

Express Encoder

import express from 'express';
import { jpackedEncoder } from 'jpacked';

const app = express();
app.use(jpackedEncoder());

app.get('/users', (req, res) => {
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];
  
  // count se calcula automáticamente
  res.jpacked(users);
  
  // O con metadatos de paginación:
  res.jpacked(users, { page: 1, pageCount: 5, total: 100 });
  
  // También puedes enviar un objeto único:
  res.jpacked({ id: 1, name: 'Alice' });
});

Express Decoder

import express from 'express';
import { jpackedDecoder } from 'jpacked';

const app = express();
// IMPORTANTE: Usar express.text() antes de jpackedDecoder()
app.use(express.text({ type: 'application/jpacked' }));
app.use(jpackedDecoder());

app.post('/data', (req, res) => {
  if (req.jpacked) {
    console.log(req.jpacked.data);
    console.log(req.jpacked.metadata);
  }
  res.send('OK');
});

Fetch Decoder (Frontend)

import { fetchJPACKED } from 'jpacked';

// Uso básico
const response = await fetchJPACKED('/api/users');
if ('data' in response) {
  console.log(response.data);      // Array de objetos
  console.log(response.metadata);   // Metadatos
} else {
  // Response normal (no JPACKED)
  const json = await response.json();
}

// Enviar datos JPACKED (POST)
import { encode, fetchJPACKED } from 'jpacked';

// Puedes enviar un array o un objeto único
const requestData = { filter: 'active', status: 'enabled' };

// count se calcula automáticamente (1 en este caso)
const jpackedBody = encode(requestData);

const result = await fetchJPACKED('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/jpacked' },
  body: jpackedBody,
});

🚀 Ejemplos por Framework

Nest.js (Backend)

1. Interceptor para Respuestas JPACKED

// src/interceptors/jpacked.interceptor.ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { encode } from 'jpacked';

@Injectable()
export class JPACKEDInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const response = context.switchToHttp().getResponse();
    
    // Solo aplicar si el cliente acepta application/jpacked
    const acceptHeader = request.headers.accept || '';
    const wantsJPACKED = acceptHeader.includes('application/jpacked');
    
    if (wantsJPACKED) {
      return next.handle().pipe(
        map((data) => {
          response.setHeader('Content-Type', 'application/jpacked');
          
          // Si data es un array o objeto, codificarlo
          if (Array.isArray(data) || (typeof data === 'object' && data !== null)) {
            return encode(data);
          }
          
          return data;
        })
      );
    }
    
    return next.handle();
  }
}

2. Controlador con JPACKED

// src/users/users.controller.ts
import { Controller, Get, Post, Body, UseInterceptors, Query } from '@nestjs/common';
import { JPACKEDInterceptor } from '../interceptors/jpacked.interceptor';
import { UsersService } from './users.service';
import { encode } from 'jpacked';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  @UseInterceptors(JPACKEDInterceptor)
  async findAll(@Query('page') page?: number) {
    const users = await this.usersService.findAll(page);
    
    // Retornar datos normalmente, el interceptor los codificará
    return users;
  }

  @Get(':id')
  @UseInterceptors(JPACKEDInterceptor)
  async findOne(@Param('id') id: string) {
    const user = await this.usersService.findOne(id);
    
    // Objeto único - se codifica automáticamente
    return user;
  }

  @Post()
  async create(@Body() createUserDto: any) {
    // Si recibes datos JPACKED, decodifícalos manualmente
    const user = await this.usersService.create(createUserDto);
    return user;
  }
}

3. Pipe para Decodificar Request Body JPACKED

// src/pipes/jpacked-body.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
import { decode } from 'jpacked';

@Injectable()
export class JPACKEDBodyPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    // Si el content-type es application/jpacked, decodificar
    if (typeof value === 'string' && value.startsWith('JPACKED/')) {
      const decoded = decode(value);
      return decoded.data.length === 1 ? decoded.data[0] : decoded.data;
    }
    
    return value;
  }
}

4. Uso del Pipe en Controlador

// src/users/users.controller.ts
import { Controller, Post, Body, UsePipes } from '@nestjs/common';
import { JPACKEDBodyPipe } from '../pipes/jpacked-body.pipe';

@Controller('users')
export class UsersController {
  @Post()
  @UsePipes(JPACKEDBodyPipe)
  async create(@Body() createUserDto: any) {
    // createUserDto ya está decodificado si venía en JPACKED
    return this.usersService.create(createUserDto);
  }
}

5. Configuración Global (Opcional)

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { JPACKEDInterceptor } from './interceptors/jpacked.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // Aplicar interceptor globalmente (opcional)
  // app.useGlobalInterceptors(new JPACKEDInterceptor());
  
  await app.listen(3000);
}
bootstrap();

Next.js (Frontend)

1. Hook Personalizado para JPACKED

// hooks/useJPACKED.ts
import { useState, useEffect } from 'react';
import { fetchJPACKED, encode } from 'jpacked';
import type { DecodeResult } from 'jpacked';

interface UseJPACKEDOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  body?: any;
  headers?: HeadersInit;
}

export function useJPACKED<T = any>(
  url: string,
  options?: UseJPACKEDOptions
) {
  const [data, setData] = useState<T[] | null>(null);
  const [metadata, setMetadata] = useState<any>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    async function fetchData() {
      try {
        setLoading(true);
        
        const fetchOptions: RequestInit = {
          method: options?.method || 'GET',
          headers: {
            'Accept': 'application/jpacked',
            ...options?.headers,
          },
        };

        // Si hay body y es POST/PUT, codificarlo en JPACKED
        if (options?.body && (options.method === 'POST' || options.method === 'PUT')) {
          fetchOptions.headers = {
            ...fetchOptions.headers,
            'Content-Type': 'application/jpacked',
          };
          fetchOptions.body = encode(options.body);
        }

        const result = await fetchJPACKED<T>(url, fetchOptions);

        if ('data' in result) {
          setData(result.data);
          setMetadata(result.metadata);
        } else {
          // Fallback a JSON si no es JPACKED
          const json = await result.json();
          setData(Array.isArray(json) ? json : [json]);
        }
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Unknown error'));
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, [url, JSON.stringify(options)]);

  return { data, metadata, loading, error };
}

2. Componente React con JPACKED

// components/UsersList.tsx
'use client';

import { useJPACKED } from '@/hooks/useJPACKED';

interface User {
  id: number;
  name: string;
  email: string;
  tags: string[];
}

export function UsersList() {
  const { data: users, metadata, loading, error } = useJPACKED<User>('/api/users', {
    headers: {
      'Accept': 'application/jpacked',
    },
  });

  if (loading) return <div>Cargando...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!users) return null;

  return (
    <div>
      {metadata && (
        <div>
          <p>Total: {metadata.total || metadata.count}</p>
          <p>Página: {metadata.page || 1}</p>
        </div>
      )}
      
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            <h3>{user.name}</h3>
            <p>{user.email}</p>
            <div>
              {user.tags?.map((tag) => (
                <span key={tag}>{tag}</span>
              ))}
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

3. API Route en Next.js (Server-Side)

// app/api/users/route.ts (App Router)
import { NextRequest, NextResponse } from 'next/server';
import { encode } from 'jpacked';

export async function GET(request: NextRequest) {
  const acceptHeader = request.headers.get('accept') || '';
  const wantsJPACKED = acceptHeader.includes('application/jpacked');

  // Simular datos de base de datos
  const users = [
    { id: 1, name: 'Alice', email: '[email protected]', tags: ['admin', 'user'] },
    { id: 2, name: 'Bob', email: '[email protected]', tags: ['user'] },
  ];

  if (wantsJPACKED) {
    const jpacked = encode(users);
    return new NextResponse(jpacked, {
      headers: {
        'Content-Type': 'application/jpacked',
      },
    });
  }

  // Fallback a JSON
  return NextResponse.json(users);
}

export async function POST(request: NextRequest) {
  const contentType = request.headers.get('content-type') || '';
  
  if (contentType.includes('application/jpacked')) {
    const body = await request.text();
    const { decode } = await import('jpacked');
    const decoded = decode(body);
    
    // Procesar decoded.data
    console.log('Datos recibidos:', decoded.data);
    
    return NextResponse.json({ success: true });
  }

  // Fallback a JSON
  const body = await request.json();
  return NextResponse.json({ success: true, data: body });
}

4. Server Action con JPACKED (Next.js 13+)

// app/actions/users.ts
'use server';

import { encode, decode } from 'jpacked';

export async function getUsers() {
  // Simular datos
  const users = [
    { id: 1, name: 'Alice', email: '[email protected]' },
    { id: 2, name: 'Bob', email: '[email protected]' },
  ];

  // Retornar en formato JPACKED
  return encode(users);
}

export async function createUser(jpackedData: string) {
  // Decodificar datos JPACKED recibidos
  const decoded = decode(jpackedData);
  const userData = decoded.data[0]; // Primer objeto
  
  // Procesar creación
  console.log('Creando usuario:', userData);
  
  return { success: true };
}

5. Uso en Componente con Server Actions

// app/users/page.tsx
import { getUsers, createUser } from '@/app/actions/users';
import { decode, encode } from 'jpacked';
import { UsersList } from '@/components/UsersList';

export default async function UsersPage() {
  // Obtener datos JPACKED del server action
  const jpackedString = await getUsers();
  const decoded = decode(jpackedString);
  
  return (
    <div>
      <h1>Usuarios</h1>
      <UsersList initialUsers={decoded.data} />
    </div>
  );
}

6. Cliente para Enviar Datos JPACKED

// lib/jpacked-client.ts
import { encode, fetchJPACKED } from 'jpacked';

export class JPACKEDClient {
  private baseUrl: string;

  constructor(baseUrl: string = '') {
    this.baseUrl = baseUrl;
  }

  async get<T = any>(endpoint: string): Promise<{ data: T[]; metadata: any }> {
    const result = await fetchJPACKED<T>(`${this.baseUrl}${endpoint}`, {
      headers: {
        'Accept': 'application/jpacked',
      },
    });

    if ('data' in result) {
      return result;
    }

    throw new Error('Expected JPACKED response');
  }

  async post<T = any>(endpoint: string, data: any): Promise<T> {
    const jpackedBody = encode(data);
    
    const result = await fetchJPACKED<T>(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/jpacked',
        'Accept': 'application/jpacked',
      },
      body: jpackedBody,
    });

    if ('data' in result) {
      return result.data[0] as T;
    }

    throw new Error('Expected JPACKED response');
  }
}

// Uso
const client = new JPACKEDClient('/api');
const users = await client.get('/users');
const newUser = await client.post('/users', { name: 'Alice', email: '[email protected]' });

📊 Comparación JSON vs JPACKED

Ejemplo: 1000 usuarios

JSON:

[
  {"id":1,"name":"Alice","email":"[email protected]","tags":["admin","user"]},
  {"id":2,"name":"Bob","email":"[email protected]","tags":["user"]},
  ...
]

Tamaño: ~85 KB

JPACKED:

JPACKED/1.1
meta[1000]
schema{id,name,email,tags[]}
data
1,Alice,[email protected],admin|user
2,Bob,[email protected],user
...

Tamaño: ~52 KB (38% más pequeño)

Ventajas de JPACKED

  1. Menor tamaño: Schema definido una vez, no repetido
  2. Mejor compresión: CSV se comprime mejor que JSON
  3. Metadatos integrados: No necesitas wrapper adicional
  4. Parsing más rápido: Estructura predefinida

Cuándo usar JPACKED

Ideal para:

  • APIs con respuestas grandes y repetitivas
  • Datos tabulares (listas, grids, tablas)
  • Transferencias donde el tamaño importa
  • Paginación con metadatos
  • Estructuras con objetos anidados (soportado desde v1.1)
  • Arrays de objetos complejos
  • Comunicación con APIs de IA (ChatGPT, Claude, etc.) - ahorra tokens significativamente
  • Envío de datos estructurados a modelos de lenguaje

No ideal para:

  • Estructuras de datos muy variadas entre filas
  • APIs con pocos datos (el overhead del schema no compensa)
  • Cuando necesitas validación de esquema estricta en tiempo de compilación

🎯 Ventajas

  1. Reducción de tamaño: 30-60% más pequeño que JSON
  2. Ahorro de tokens para IA: 30-40% menos tokens que JSON (ideal para ChatGPT, Claude, etc.)
  3. Rendimiento: Parsing más rápido en estructuras repetitivas
  4. Metadatos nativos: Paginación integrada sin wrappers
  5. Type-safe: TypeScript completo
  6. Fácil integración: Middlewares listos para usar
  7. Uso puntual: Perfecto para enviar datos a APIs de IA sin necesidad de middleware

⚠️ Limitaciones

  1. Schema fijo: Todas las filas deben tener la misma estructura (mismos campos)
  2. No es estándar: Formato propietario, requiere librería para parsear
  3. Tipos inferidos: Los tipos se infieren automáticamente (números, booleanos, JSON), pero no hay validación de esquema estricta

Nota: Los objetos anidados están completamente soportados usando el formato JPACKED con schema anidado y aplanamiento interno, lo que permite estructuras complejas sin límite de profundidad y sin aumentar significativamente el tamaño de los datos.

🤖 Uso con APIs de IA

JPACKED es especialmente útil para comunicación con modelos de IA (ChatGPT, Claude, etc.) ya que reduce significativamente el número de tokens utilizados, lo que se traduce en:

  • 💰 Ahorro de costos: Menos tokens = menos costo por request
  • Respuestas más rápidas: Menos tokens para procesar
  • 📊 Más contexto: Puedes enviar más datos con el mismo límite de tokens

Benchmarks de Tokens

Ejemplo: 100 usuarios con datos completos

| Formato | Tokens (aprox.) | Reducción | |---------|----------------|-----------| | JSON | ~2,850 tokens | - | | JPACKED | ~1,720 tokens | 40% menos tokens |

Ejemplo: 1000 registros de datos estructurados

| Formato | Tokens (aprox.) | Reducción | |---------|----------------|-----------| | JSON | ~28,500 tokens | - | | JPACKED | ~17,200 tokens | 40% menos tokens |

Cálculo aproximado basado en tokenización estándar (1 token ≈ 4 caracteres)

Ejemplo: Enviar Datos a ChatGPT

import { encode } from 'jpacked';
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Datos estructurados que quieres enviar
const users = [
  { id: 1, name: 'Alice', email: '[email protected]', role: 'admin', active: true },
  { id: 2, name: 'Bob', email: '[email protected]', role: 'user', active: true },
  // ... 98 usuarios más
];

// Opción 1: Enviar como JSON (más tokens)
const jsonData = JSON.stringify(users);
// Tamaño: ~8,500 caracteres ≈ 2,125 tokens

// Opción 2: Enviar como JPACKED (menos tokens)
const jpackedData = encode(users);
// Tamaño: ~5,100 caracteres ≈ 1,275 tokens
// Ahorro: ~850 tokens (40% menos)

// Enviar a ChatGPT
const response = await openai.chat.completions.create({
  model: 'gpt-4',
  messages: [
    {
      role: 'system',
      content: 'Eres un analista de datos. Analiza los datos proporcionados en formato JPACKED.'
    },
    {
      role: 'user',
      content: `Analiza estos datos de usuarios:\n\n${jpackedData}\n\nFormato: JPACKED/1.1`
    }
  ],
});

// ChatGPT puede entender el formato JPACKED si le explicas la estructura
// O puedes decodificarlo antes de enviarlo

Ejemplo: Análisis de Datos con IA

import { encode, decode } from 'jpacked';
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Generar datos estructurados
const salesData = [
  { product: 'Laptop', price: 999, quantity: 5, date: '2025-01-15' },
  { product: 'Mouse', price: 25, quantity: 20, date: '2025-01-15' },
  { product: 'Keyboard', price: 75, quantity: 10, date: '2025-01-16' },
  // ... más datos
];

// Codificar en JPACKED para ahorrar tokens
const jpacked = encode(salesData);

// Enviar a ChatGPT con instrucciones para parsear JPACKED
const prompt = `Analiza estos datos de ventas en formato JPACKED:

${jpacked}

El formato JPACKED tiene esta estructura:
- Primera línea: JPACKED/1.1
- Segunda línea: meta[COUNT] (número de registros)
- Tercera línea: schema{field1,field2,...} (estructura de datos)
- Línea "data" seguida de filas CSV

Por favor, analiza los datos y proporciona:
1. Total de ventas
2. Producto más vendido
3. Día con más ventas`;

const response = await openai.chat.completions.create({
  model: 'gpt-4',
  messages: [
    { role: 'user', content: prompt }
  ],
});

console.log(response.choices[0].message.content);

Ejemplo: Uso Puntual sin Middleware

import { encode, decode } from 'jpacked';

// Escenario: Enviar datos históricos a una API de IA para análisis
const historicalData = [
  { date: '2025-01-01', temperature: 22, humidity: 65, pressure: 1013 },
  { date: '2025-01-02', temperature: 24, humidity: 68, pressure: 1015 },
  // ... miles de registros
];

// Codificar para ahorrar tokens
const jpacked = encode(historicalData);

// Enviar directamente a la API
const apiResponse = await fetch('https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: 'Eres un experto en análisis de datos. Los datos vienen en formato JPACKED.'
      },
      {
        role: 'user',
        content: `Analiza estos datos meteorológicos:\n\n${jpacked}\n\nFormato: JPACKED/1.1 con schema{date,temperature,humidity,pressure}`
      }
    ],
    max_tokens: 1000,
  }),
});

const result = await apiResponse.json();
console.log(result.choices[0].message.content);

Ejemplo: Procesar Respuesta de IA que Retorna JPACKED

import { decode } from 'jpacked';
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Pedir a ChatGPT que retorne datos en formato JPACKED
const response = await openai.chat.completions.create({
  model: 'gpt-4',
  messages: [
    {
      role: 'system',
      content: 'Cuando retornes datos estructurados, usa formato JPACKED para ahorrar tokens.'
    },
    {
      role: 'user',
      content: 'Genera una lista de 10 productos con nombre, precio y categoría en formato JPACKED'
    }
  ],
});

// Decodificar la respuesta JPACKED
const jpackedResponse = response.choices[0].message.content;
// Extraer solo la parte JPACKED (puede venir con texto explicativo)
const jpackedMatch = jpackedResponse.match(/JPACKED\/1\.1[\s\S]*/);
if (jpackedMatch) {
  const decoded = decode(jpackedMatch[0]);
  console.log('Productos:', decoded.data);
  console.log('Metadatos:', decoded.metadata);
}

Ventajas para IA

  1. Ahorro de Tokens: 30-40% menos tokens que JSON
  2. Más Datos por Request: Puedes enviar más información dentro del límite de tokens
  3. Estructura Clara: El schema hace que los datos sean más fáciles de entender para la IA
  4. Formato Compacto: Ideal para contextos largos donde cada token cuenta

📈 Benchmarks

Tamaño (1000 registros)

| Formato | Tamaño | Reducción | |---------|--------|-----------| | JSON | 85 KB | - | | JPACKED | 52 KB | 38% | | JSON (gzip) | 12 KB | - | | JPACKED (gzip) | 8 KB | 33% |

Tokens (1000 registros)

| Formato | Tokens (aprox.) | Reducción | |---------|----------------|-----------| | JSON | ~21,250 tokens | - | | JPACKED | ~13,000 tokens | 39% menos tokens |

Cálculo: 1 token ≈ 4 caracteres (promedio)

Velocidad de Parsing (1000 registros)

| Formato | Tiempo | Mejora | |---------|--------|--------| | JSON.parse | 2.1ms | - | | JPACKED.decode | 1.4ms | 33% más rápido |

Benchmarks realizados en Node.js v20, datos simulados

🤝 Contribuir

Las contribuciones son bienvenidas! Por favor:

  1. Fork el proyecto
  2. Crea una rama para tu feature (git checkout -b feature/AmazingFeature)
  3. Commit tus cambios (git commit -m ':art: Add some AmazingFeature')
  4. Push a la rama (git push origin feature/AmazingFeature)
  5. Abre un Pull Request

📄 Licencia

Este proyecto está licenciado bajo la Licencia MIT - ver el archivo LICENSE para más detalles.

📚 Documentación Técnica

Para más detalles sobre la especificación del formato, consulta RFC.md.


JPACKED - Compact data format for modern APIs 🚀