Skip to content

πŸͺ„ URL query encoder/decoder with a user configuration

License

Notifications You must be signed in to change notification settings

xsolla/lfg-query-coder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

34 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@lfgroup/query-coder

minified + gzipped

URL query coder/decoder with configurable user pattern. It provides the most comfortable experience for encoding and decoding complex nested filter objects. Check out example for more info.

  1. Quickstart
  2. Advanced example
  3. Usage
    1. QueryCoder
      1. Encoding and decoding
      2. Decoding with a default value
    2. QueryHandler
      1. Ignoring required fields
      2. Defining value type
      3. Aliasing query values
      4. Encoding non-string values
      5. Accessing handlers outside of a coder
      6. Using handlers between different coders
      7. Excluding values from encoding

Quickstart

yarn add @lfgroup/query-coder

Query coder is designed to repeat an interface of provided object with QueryCoder, only replacing tree leaves with parser instances β€” QueryHandlers.

WIP

Advanced example

WIP

Imaging having a such interface for your querying filters:

type SearchGroupsFilter = {
  gameId?: "WorldOfWarcraft" | "WildRift" | "LostArk";
  gameMode?: GameMode;
  from?: Date;
  language?: Language;

  lostArk?: LostArkFilterInput;
  wildRift?: WildRiftFilterInput;
  wow?: WowFilterInput;
};

Let's take 1 example of this:

const filters: SearchGroupsFilter = {
  gameId: "WorldOfWarcraft",
  gameMode: GameMode.WowMythicPlus,
  language: Language.En,
  wow: {
    faction: WowFaction.Alliance,
    dungeon: WowDungeon.MistsOfTirnaScithe,
    minRioRating: 1400,
    region: WowRegion.Europe,
  },
};

Which should result in this:

const query =
  "game=wow&mode=mplus&language=en&faction=alliance&dungeon=mots&rating=1400&region=eu";

So we want to:

  • flatten nested filter object in a query string
  • rename certain keys (gameMode -> mode)
  • re-map certain values (WorldOfWarcraft -> wow)

With this lib, you can init QueryCoder and that's it! πŸŽ‰

import { QueryCoder, QueryHandler, Type } from "@lfg/query-coder";

// passing generic interface checks, whether node keys
// are the same as in provided interface
const query = new QueryCoder<SearchGroupsFilter>({
  gameId: new QueryHandler({
    query: "game",
    aliases: {
      WorldOfWarcraft: "wow",
      WildRift: "wr",
      LostArk: "la",
    },
  }),
  gameMode: new QueryHandler({ query: "mode" }),
  language: new QueryHandler({ query: "language" }),
  wow: {
    faction: new QueryHandler({
      query: "faction",
      /**
       * decodeCondition is Partial of generic
       * if decodeCondition is set, search query would be handled
       * with this handler only if decodeCondition matches query
       * For more info, check out section below
       */
      decodeCondition: { gameId: "WorldOfWarcraft" },
    }),
    dungeon: new QueryHandler({
      query: "dungeon",
      decodeCondition: { gameId: "WorldOfWarcraft" },
    }),
    minRioRating: new QueryHandler({
      query: "minRioRating",
      decodeCondition: { gameId: "WorldOfWarcraft" },
      /**
       * You should provide a primitive type, which is different from string
       * Otherwise, url query "foo=313" can result in an unexpected result
       */
      decodeType: Type.Number,
    }),
    region: new QueryHandler({
      query: "region",
      decodeCondition: { gameId: "WorldOfWarcraft" },
    }),
  },
});

query.encode(filters).toString(); // should result in query variable
query.decode(query); // should result in filters variable

Usage

1. QueryCoder

QueryCoder is a main instance you will use to encode and to decode your data. Typically, you should create coders at a global scope and provide a generic of an interface you are planing to encode/decode.

import { QueryCoder, QueryHandler } from "@lfg/query-coder";


interface ObjectToSerialize {
  foo: string;
  bar: {
    baz: string;
  };
}

const coder = new QueryCoder<ObjectToSerialize>({
  foo: new QueryHandler({ query: "foo_in_query" }),
  bar: {
    baz: new QueryHandler({ query: "baz_in_query" }),
  },
});

Encoding and decoding

Using a coder we've created above we can encode object to url query or decode query string to an object:

const object: ObjectToSerialize = {
  foo: 'value of foo',
  bar: {
    baz: 'bazz-value'
  },
};

// Encoding with .encode(T) method, which returns URLSearchParams
const urlSearchParams = coder.encode(object);
console.log(urlSearchParams.toString()); // foo_in_query=value%20of%20foo&baz_in_query=bazz-value

// Decoding with .decode(string) method, which returns T
const decodedObject = coder.decode(urlSearchParams.toString());
console.log(decodedObject); // same as var `object`

Decoding with a default value

Sometimes you may want to provide a default value for decoding proccess to fill required gaps of an interface. Providing default value will deep assign decoded object to default object, overwriting default value with decoded if any provided.

const query = `foo_in_query=url-value`


const defaultObj: ObjectToSerialize = {
  foo: "default",
  bar: {
    baz: "default",
  },
};

const decodedObj = coder.decode(query, { defaultValue: defaultObj });

console.log(decodedObj) // { foo: "url-value", bar: { baz: "default" } }

2. QueryHandler

QueryHandler is a handler for each node leaf of a codable object. It must include a name for a query key and provides additional options. You should mind that:

  • Only leaves, that have QueryHandler will be encoded/decoded
  • Values that are not strings should has decodeType param
interface ObjectToSerialize {
  foo: string;
  bar: {
    baz: string;
  };
}

const coder = new QueryCoder<ObjectToSerialize>({
  foo: new QueryHandler({ query: "foo_in_query" }),
  bar: {
    baz: new QueryHandler({ query: "baz_in_query" }),
  },
});

Ignoring required fields

Refer decode default

Defining value type

Aliasing query values

Encoding non-string values

Accessing handlers outside of a coder

Using handlers between different coders

Excluding values from encoding