In this blog post, we’ll create a simple TypeScript module for parsing Vehicle Identification Numbers (VINs). VIN is a unique 17-character code the automotive industry uses to identify individual vehicles.
To get started, we’ll need to install TypeScript and Jest:
npm install typescript jest @types/jest ts-jest
Next, configure Jest to work with TypeScript by creating a jest.config.js
file:
module.exports = { preset: 'ts-jest', testEnvironment: 'node', };
Now, let’s write the TypeScript code for parsing VINs. Create a file named vinParser.ts
:
export interface VINInfo { wmi: string; vds: string; vis: string; manufacturer: string; modelYear: number; plantCode: string; } export class VINParser { private static readonly VIN_REGEX = /^(?:(?![IiOoQq])[A-HJ-NPR-Za-hj-npr-z0-9]){17}$/; private static readonly WMI_MAP = { "1HG": "Honda", "1N4": "Nissan", "1FA": "Ford", "1FT": "Ford", "1G1": "Chevrolet", "1G6": "Cadillac", "1GC": "Chevrolet", "2HG": "Honda", "2T1": "Toyota", "3N1": "Nissan", "4T1": "Toyota", "5J6": "Honda", "5N1": "Nissan", "5TDB": "Toyota", "5TF": "Toyota", "JHM": "Honda", "JN1": "Nissan", "JT3": "Toyota", "JTD": "Toyota", "WBA": "BMW", "WBS": "BMW M", "WDB": "Mercedes-Benz", "WP0": "Porsche", "ZAM": "Maserati", "KM8": "Hyundai", "KMH": "Hyundai", "5NP": "Hyundai", // Add more World Manufacturer Identifiers and their corresponding manufacturers here }; private static readonly YEAR_MAP = { "1": 2001, "2": 2002, "3": 2003, "4": 2004, "5": 2005, "6": 2006, "7": 2007, "8": 2008, "9": 2009, "A": 2010, "B": 2011, "C": 2012, "D": 2013, "E": 2014, "F": 2015, "G": 2016, "H": 2017, "J": 2018, "K": 2019, "L": 2020, "M": 2021, "N": 2022, "P": 2023, "R": 2024, "S": 2025, "T": 2026, "V": 2027, "W": 2028, "X": 2029, "Y": 2030, "1": 2031, "2": 2032, "3": 2033, "4": 2034, "5": 2035, "6": 2036, "7": 2037, "8": 2038, "9": 2039, // This map can be extended for years beyond 2039, following the pattern established by the ISO standards }; public static parse(vin: string): VINInfo | null { if (!this.validate(vin)) { return null; } const wmi = vin.slice(0, 3); const vds = vin.slice(3, 9); const vis = vin.slice(9, 17); const manufacturer = this.WMI_MAP[wmi] || "Unknown"; const modelYear = this.YEAR_MAP[vin[9].toUpperCase() as keyof typeof VINParser.YEAR_MAP] || 0; const plantCode = vin[10]; return { wmi, vds, vis, manufacturer, modelYear, plantCode, }; } public static validate(vin: string): boolean { return this.VIN_REGEX.test(vin); } }
In this code, we define a class VINParser
with a single method validate
. It takes a VIN string as input and returns a boolean indicating whether the VIN is valid. We use a regular expression to ensure the VIN is 17 characters long and contains no illegal characters (I, O, and Q).
Remember that this map will need to be updated in 2040 and beyond, but the current ISO standard doesn’t provide a direct mapping for those years. The best approach for handling the model year would be to follow the ISO standards and update the map when new rules are defined.
The code above includes more World Manufacturer Identifiers (WMIs) for common manufacturers. You can further extend the WMI_MAP
as needed to include additional manufacturers or more specific plant codes.
Now, let’s write Jest unit tests for the VINParser class. Create a file named vinParser.test.ts
:
import { VINParser } from './vinParser'; describe('VINParser', () => { test('should return true for valid VINs', () => { const validVins = [ '1FTEW1EP9GKF12229', '1FTFX1EF2GKD50050', '5J6RM3H32EL012345', ]; for (const vin of validVins) { expect(VINParser.validate(vin)).toBe(true); } }); test('should return false for invalid VINs', () => { const invalidVins = [ '1FTEW1EP9GKF1222', // Too short '1FTFX1EF2GKD5005Q', // Contains an illegal character '5J6RM3H32EL01234X', // Too long ]; for (const vin of invalidVins) { expect(VINParser.validate(vin)).toBe(false); } }); test('should parse valid VINs', () => { const validVins = [ { vin: '1FTEW1EP9GKF12229', manufacturer: 'Ford', modelYear: 2016, plantCode: 'K', }, { vin: '5J6RM3H32EL012345', manufacturer: 'Honda', modelYear: 2014, plantCode: 'L', }, { vin: 'KMHGC4DD8CU012345', manufacturer: 'Hyundai', modelYear: 2012, plantCode: 'U', }, ]; for (const { vin, manufacturer, modelYear, plantCode } of validVins) { const parsedVin = VINParser.parse(vin); expect(parsedVin).not.toBeNull(); expect(parsedVin!.manufacturer).toBe(manufacturer); expect(parsedVin!.modelYear).toBe(modelYear); expect(parsedVin!.plantCode).toBe(plantCode); } }); test('should return null for invalid VINs', () => { const invalidVins = [ '1FTEW1EP9GKF1222', // Too short '1FTFX1EF2GKD5005Q', // Contains an illegal character '5J6RM3H32EL01234X', // Too long ]; for (const vin of invalidVins) { expect(VINParser.parse(vin)).toBeNull(); } }); });
To run the tests, add a script to your package.json
file:
{ "scripts": { "test": "jest" } }
Now you can run the tests with npm test
.
You can further extend this code to parse additional information from the VIN, such as the manufacturer, model year, and production plant. You can find more information about the structure of VINs here.