@jsarc/qust
v0.0.1-beta.1.2
Published
Qust est une bibliothèque TypeScript ultra-légère et performante pour la sérialisation et désérialisation d'objets en chaînes de requête (query strings). Elle offre une API simple et puissante pour manipuler les paramètres d'URL avec support des types com
Readme
@jsarc/qust
@jsarc/qust est une bibliothèque TypeScript ultra-légère et performante pour la sérialisation et désérialisation d'objets en chaînes de requête (query strings). Elle offre une API simple et puissante pour manipuler les paramètres d'URL avec support des types complexes, des tableaux, des objets imbriqués et des options avancées.
✨ Fonctionnalités
- Sérialisation complète : Conversion d'objets JavaScript/TypeScript en query strings
- Parsing intelligent : Reconstruction d'objets à partir de query strings
- Support multi-format : Tableaux au format bracket, index, comma ou séparateur personnalisé
- Objets imbriqués : Profondeur configurable pour les structures complexes
- Filtrage avancé : Exclusion des valeurs nulles, chaînes vides ou selon vos critères
- Encodage/décodage : Contrôle total sur l'encodage URI
- Type-safe : Typage TypeScript complet avec génériques
- Léger : Seulement ~3.5KB minifié
- Universal : Compatible navigateur, Node.js, Deno, Bun, etc.
📦 Installation
Via npm/yarn/pnpm
npm install @jsarc/qust
# ou
yarn add @jsarc/qust
# ou
pnpm add @jsarc/qustImportation directe (CDN)
<script src="@jsarc/qust/qust.all.js"></script>🚀 Démarrage Rapide
TypeScript/ES Modules
import { qust, Qust } from '@jsarc/qust';
// Utilisation avec les fonctions utilitaires
const query = qust.stringify({ name: "John", age: 30 });
// → "?name=John&age=30"
const obj = qust.parse("?name=John&age=30");
// → { name: "John", age: 30 }CommonJS
const { qust } = require('@jsarc/qust');Navigateur (global)
<script src="@jsarc/qust/qust.all.js"></script>
<script>
// Disponible globalement
const query = qust.stringify({ name: "John", age: 30 });
const obj = qust.parse(query);
</script>📚 API Référence
Types
type Primitive = string | number | boolean | null | undefined;
type QueryValue = Primitive | Primitive[] | { [key: string]: QueryValue };
type QueryObject = { [key: string]: QueryValue };
interface QustOptions {
arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'none';
arraySeparator?: string;
skipNull?: boolean;
skipEmptyString?: boolean;
encode?: boolean;
decode?: boolean;
depth?: number;
}Classe Qust
Constructeur
new Qust(options?: QustOptions)Crée une nouvelle instance avec des options personnalisées.
Méthodes
stringify(obj: QueryObject): string: Convertit un objet en query stringparse<T = QueryObject>(queryString: string): T: Convertit une query string en objetsetOptions(newOptions: Partial<QustOptions>): void: Met à jour les optionsgetOptions(): Required<QustOptions>: Retourne les options actuelles
Fonctions utilitaires globales
// stringify avec options par défaut
qust.stringify(obj: QueryObject, options?: QustOptions): string
// parse avec options par défaut
qust.parse<T = QueryObject>(queryString: string, options?: QustOptions): TOptions par défaut
const DEFAULT_OPTIONS = {
arrayFormat: 'bracket',
arraySeparator: ',',
skipNull: true,
skipEmptyString: false,
encode: true,
decode: true,
depth: 10
};🔧 Utilisation Détaillée
Format des tableaux
Qust supporte plusieurs formats pour les tableaux :
1. Format "bracket" (défaut)
const obj = { tags: ["js", "ts", "node"] };
qust.stringify(obj);
// → "?tags[]=js&tags[]=ts&tags[]=node"
qust.parse("?tags[]=js&tags[]=ts&tags[]=node");
// → { tags: ["js", "ts", "node"] }2. Format "index"
const obj = { coords: [10, 20, 30] };
qust.stringify(obj, { arrayFormat: "index" });
// → "?coords[0]=10&coords[1]=20&coords[2]=30"
qust.parse("?coords[0]=10&coords[1]=20&coords[2]=30");
// → { coords: [10, 20, 30] }3. Format "comma"
const obj = { values: [1, 2, 3] };
qust.stringify(obj, { arrayFormat: "comma" });
// → "?values=1,2,3"
qust.parse("?values=1,2,3", { arrayFormat: "comma" });
// → { values: [1, 2, 3] }4. Format "separator"
const obj = { items: ["a", "b", "c"] };
qust.stringify(obj, {
arrayFormat: "separator",
arraySeparator: "|"
});
// → "?items=a&items=b&items=c"
// Chaque élément devient un paramètre distinct avec la même clé5. Format "none"
const obj = { items: ["x", "y"] };
qust.stringify(obj, { arrayFormat: "none" });
// → "?items=x&items=y"
// Similaire à separator mais géré différemment en interneObjets imbriqués
const obj = {
user: {
name: "Alice",
settings: {
theme: "dark",
notifications: true
}
}
};
const query = qust.stringify(obj);
// → "?user[name]=Alice&user[settings][theme]=dark&user[settings][notifications]=true"
const parsed = qust.parse(query);
// → { user: { name: "Alice", settings: { theme: "dark", notifications: true } } }Contrôle de la profondeur
// Limite la profondeur de sérialisation/désérialisation
const q = new Qust({ depth: 2 });
const obj = {
a: {
b: {
c: { // Ce niveau sera ignoré (profondeur > 2)
d: 10
}
}
}
};
q.stringify(obj);
// Avertissement : "Qust: Profondeur maximale atteinte"Filtrage des valeurs
const obj = {
a: null,
b: "",
c: "value",
d: 0,
e: undefined
};
// Ignore null et chaînes vides
qust.stringify(obj, {
skipNull: true,
skipEmptyString: true
});
// → "?c=value&d=0"
// Garde toutes les valeurs
qust.stringify(obj, {
skipNull: false,
skipEmptyString: false
});
// → "?a=null&b=&c=value&d=0"Contrôle de l'encodage
const obj = { city: "Paris & Lyon" };
// Avec encodage (défaut)
qust.stringify(obj);
// → "?city=Paris%20%26%20Lyon"
// Sans encodage
qust.stringify(obj, { encode: false });
// → "?city=Paris & Lyon"
// Parsing avec/sans décodage
qust.parse("?city=Paris%20%26%20Lyon", { decode: true });
// → { city: "Paris & Lyon" }
qust.parse("?city=Paris%20%26%20Lyon", { decode: false });
// → { city: "Paris%20%26%20Lyon" }Types de données supportés
const obj = {
string: "hello",
number: 42,
boolean: true,
null: null,
undefined: undefined,
array: [1, 2, 3],
nested: { key: "value" }
};
qust.stringify(obj);
// Les valeurs sont converties automatiquement :
// - boolean → "true"/"false"
// - number → chaîne numérique
// - null → "null"
// - undefined → "undefined"
qust.parse("?string=hello&number=42&boolean=true&null=null");
// → { string: "hello", number: 42, boolean: true, null: null }🎯 Cas d'utilisation
1. Construction d'URLs
function buildSearchUrl(baseUrl: string, filters: any): string {
const query = qust.stringify(filters);
return `\${baseUrl}\${query}`;
}
const filters = {
q: "laptop",
category: "electronics",
price: { min: 100, max: 1000 },
brands: ["dell", "hp", "lenovo"],
inStock: true
};
const url = buildSearchUrl("/products", filters);
// → "/products?q=laptop&category=electronics&price[min]=100&price[max]=1000&brands[]=dell&brands[]=hp&brands[]=lenovo&inStock=true"2. Récupération des paramètres d'URL
// Dans une application web
const currentQuery = (window as any).location.search;
const params = qust.parse(currentQuery);
// Utilisation avec React/Vue/Angular
function useQueryParams() {
const [params, setParams] = useState(() => {
return qust.parse((window as any).location.search);
});
const updateParams = (newParams: any) => {
const query = qust.stringify({ ...params, ...newParams });
(window as any).history.pushState({}, '', `?\${query}`);
setParams(qust.parse(query));
};
return [params, updateParams];
}3. Communication API
// Client-side
async function fetchWithParams(endpoint: string, params: any) {
const query = qust.stringify(params);
const response = await fetch(`\${endpoint}\${query}`);
return response.json();
}
// Server-side (Node.js/Express)
app.get('/api/data', (req, res) => {
const params = qust.parse(req.url);
// Traiter les paramètres...
res.json({ data: params });
});4. Sauvegarde d'état
class FormState {
private state: any = {};
saveToUrl() {
const query = qust.stringify(this.state, {
skipEmptyString: true,
skipNull: true
});
(window as any).location.hash = query;
}
loadFromUrl() {
const query = (window as any).location.hash.substring(1);
this.state = qust.parse(`?\${query}`) || {};
}
}🔧 Configuration Avancée
Instance personnalisée
// Création d'une instance avec configuration spécifique
const customQust = new Qust({
arrayFormat: 'comma',
skipNull: true,
skipEmptyString: true,
depth: 5
});
// Réutilisation avec la même configuration
const query1 = customQust.stringify(obj1);
const query2 = customQust.stringify(obj2);
// Modification dynamique
customQust.setOptions({ arrayFormat: 'index' });Combinaison avec d'autres bibliothèques
import { qust } from '@jsarc/qust';
import {
StringSchema,
NumberSchema,
BooleanSchema,
DateSchema,
EnumSchema,
NotEnumSchema,
ArraySchema,
FileSchema,
ObjectSchema,
ChosenTypeSchema,
AnyTypeSchema,
} from '@jsarc/jon';
const JON = {
'String': StringSchema,
'Number': NumberSchema,
'Boolean': BooleanSchema,
'Date': DateSchema,
'Enum': EnumSchema,
'NotEnum': NotEnumSchema,
'Array': ArraySchema,
'File': FileSchema,
'Object': ObjectSchema,
'ChosenType': ChosenTypeSchema,
'AnyType': AnyTypeSchema,
}; // Pour la validation
// Validation avant sérialisation
const schema = new JON.Object('fr').struct({
name: new JON.String('fr').required(),
age: new JON.Number('fr').min(0).max(150),
tags: new JON.Array('fr').types(new JON.String('fr'))
});
function safeStringify(obj: any) {
const validation = schema.check(obj);
if (validation.valid) {
return qust.stringify(obj);
}
throw new Error(`Validation failed: \${validation.errors}`);
}📋 Table des formats de tableaux
| Format | Exemple de sortie | Description |
|--------|-------------------|-------------|
| bracket | tags[]=a&tags[]=b | Format standard avec crochets vides |
| index | tags[0]=a&tags[1]=b | Avec indices explicites |
| comma | tags=a,b | Séparés par des virgules |
| separator | tags=a&tags=b | Paramètres multiples avec même clé |
| none | tags=a&tags=b | Similaire à separator, traitement interne différent |
🚨 Gestion des cas limites
Tableaux vides
qust.stringify({ items: [] });
// → "" (par défaut, les tableaux vides sont ignorés)Valeurs spéciales
qust.stringify({
special: "a&b=c?d#e",
spaces: "hello world",
unicode: "🎉"
});
// → "?special=a%26b%3Dc%3Fd%23e&spaces=hello%20world&unicode=%F0%9F%8E%89"
// Tout est correctement encodéConflits de clés
// Gestion automatique des structures complexes
const obj = {
"user[name]": "direct", // Clé avec crochets
user: { name: "nested" } // Objet imbriqué
};
qust.stringify(obj);
// Les deux sont correctement gérés🔬 Performance
Qust est optimisé pour la performance :
- Algorithmes récursifs optimisés avec contrôle de profondeur
- Encodage/décodage sélectif pour éviter les opérations inutiles
- Gestion mémoire efficace avec réutilisation d'objets
- Parsing streaming pour les grandes chaînes
// Benchmark approximatif (sur Node.js v18)
const largeObj = {
users: Array(1000).fill(0).map((_, i) => ({
id: i,
name: `User\${i}`,
data: { nested: { value: i * 2 } }
}))
};
console.time('stringify');
const query = qust.stringify(largeObj);
console.timeEnd('stringify'); // ~50ms
console.time('parse');
const parsed = qust.parse(query);
console.timeEnd('parse'); // ~30ms🔧 Build et Développement
Structure du projet
@jsarc/qust/
├── qust.all.js
├── qust.all.min.js
├── index.d.ts
├── index.js
├── index.min.d.ts
├── index.min.js
├── package.json
├── tsconfig.json
└── README.md📋 Compatibilité
Navigateurs Supportés
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
- Opera 47+
- iOS Safari 12+
- Android Chrome 60+
Environnements
- Node.js 18+
- Deno 1.30+
- Bun 1.0+
- React Native
- Electron
- Cloudflare Workers
- Vercel Edge Functions
Dépendances
- Aucune dépendance externe : Qust est entièrement autonome
- TypeScript : Support natif (types inclus)
- ES6+ : Utilise les fonctionnalités modernes JavaScript
🛡️ Meilleures Pratiques
Sécurité
- Toujours encoder par défaut : Protège contre les injections
- Valider les entrées : Avant de parser des données non fiables
- Limiter la profondeur : Éviter les attaques par récursion
- Utiliser skipEmptyString : Pour les formulaires web
// Configuration sécurisée par défaut
const secureQust = new Qust({
encode: true, // Toujours encoder
depth: 10, // Limite raisonnable
skipNull: true, // Ignorer les valeurs nulles
skipEmptyString: true // Ignorer les champs vides
});Performance
- Réutiliser les instances : Pour éviter la recréation d'options
- Choisir le bon format : "comma" pour les grands tableaux
- Filtrer tôt : skipNull/skipEmptyString réduit la charge
- Éviter la sur-sérialisation : Ne pas sérialiser inutilement
Maintenance
- Documenter les schémas : Utiliser TypeScript pour la documentation
- Tests unitaires : Couvrir les cas d'utilisation
- Versionner les APIs : Changements de format d'arrayFormat
- Logging en dev : Activer les avertissements de profondeur
📄 Licence
MIT License - Voir le fichier LICENSE pour plus de détails.
Copyright (c) 2024 INICODE
Permission est accordée, gratuitement, à toute personne obtenant une copie de ce logiciel et des fichiers de documentation associés (le "Logiciel"), de traiter dans le Logiciel sans restriction, y compris sans limitation les droits d'utilisation, de copie, de modification, de fusion, de publication, de distribution, de sous-licence et/ou de vente de copies du Logiciel, et de permettre aux personnes à qui le Logiciel est fourni de le faire, sous réserve des conditions suivantes :
🐛 Signaler un Bug
Envoyez nous un mail à l'adresse [email protected] pour :
- Signaler un bug
- Proposer une amélioration
- Poser une question
@jsarc/qust - La solution ultime pour la manipulation de query strings en TypeScript.
Développé par l'équipe INICODE
