0

How do I ensure that my string have two words in it using typescript.

The reason I need this so that I would call server only if name is in "first last" name format.

Ashish
  • 14,295
  • 21
  • 82
  • 127
  • 1
    `"first last".split(' ').length === 2` – Keith Sep 22 '17 at 20:08
  • Thank you Keith. – Ashish Sep 22 '17 at 20:15
  • 1
    This might not give expected results if you have newlines or other whitespace in your string – JKillian Sep 22 '17 at 20:22
  • 1
    Or if you want to be more general, you can use regex to split at each one or more space character (i.e space, new line ... etc): `str.split(/\s+/).length === 2` – Ammar Sep 22 '17 at 20:25
  • @JKillian Why would it be unexpected?, we don't want Tab's / Newlines. etc. and is why regEx `\s+` is not ideal either. Or was you talking to somebody else :) – Keith Sep 22 '17 at 20:32
  • @Keith `"fi\t\trst last".split(' ').length === 2` is true, but it probably shouldn't be valid – JKillian Sep 22 '17 at 20:42
  • @JKillian Good point, but in some respects that another check on `first` / `last`... The firstname in this case is wrong. Of course how deep a check you do is a design decision. One of the reason's I up-voted @ScottMarcus answer as it was more specific. And even then it's not perfect, eg. John O'neal etc. – Keith Sep 22 '17 at 20:49
  • Is this for a system people are going to use? Do these requirements fall into the category of [falsehoods programmers believe about names](http://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/)? – David Sherret Sep 22 '17 at 20:56

3 Answers3

4

The answer really isn't TypeScript dependent. It's basic JavaScript.

You can use a regular expression to perform a test on the string:

function testString(input){
 // Return whether or not there are letters (any amount and any case)
 // followed by one space and then more letters (any amount and any case).
 // Of course, if you wanted to get more specific about case or character
 // counts, adjusting the regular expression would be simple.
 return /^[A-Za-z]+ [A-Za-z]+$/.test(input);
}

console.log(testString("Scott"));
console.log(testString("Scott Marcus"));
console.log(testString("Scott\nMarcus"));
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • I would have thought `^[A-z]+ [A-z]+$`, otherwise this will make `first\nname` `first\tname` etc valid too. – Keith Sep 22 '17 at 20:37
  • `/^[A-z]+ [A-z]+$/.test("[\\]^_aBc AbC[\\]^_")` returns true. Don't use `[A-z]` when you mean `[A-Za-z]`. Or consider using the case insensitive flag, `i`. – kamoroso94 Sep 22 '17 at 20:50
  • @kamoroso94 Updated. – Scott Marcus Sep 22 '17 at 20:51
  • What if the name has an apostrophe in it? Hyphenated last names? Or names with accents? – David Sherret Sep 22 '17 at 20:57
  • @DavidSherret Then, I guess we play this game longer. Of course, we'd update the RegEx to accommodate. What if "Cher" or "Bono" want to fill out our form? – Scott Marcus Sep 22 '17 at 20:59
  • @ScottMarcus exactly... an issue is that the questioner is asking the wrong question. You might want to mention in your answer the problems with this approach for names or in general with words that aren't `/[a-z]+/i`. – David Sherret Sep 22 '17 at 21:44
  • @DavidSherret I think you just did! Thanks. – Scott Marcus Sep 22 '17 at 21:46
0

Things like this can't be typed with TypeScript, but you can make use of type guards to explicitly indicate that a param value needs to pass a specific test first to be a valid input to a function.

Here's what a "strongly typed" version of Scott's answer could be:

my-custom-types.d.ts

// A string supertype that represents a string that passed our RegEx test
declare type TwoWords = Branded<string, 'two-words'>

// Used to declare an unique primitive type, "nominal types" aren't supported yet
// @see https://github.com/Microsoft/TypeScript/issues/202
declare type Branded<T, U> = T & { '__brand': U }

// FullName in a "first last" name format
declare type FullName = TwoWords

my-test.ts

// This a type guard that casts strings with two words to TwoWords
function isTwoWords(input: string): input is TwoWords {
    return /^[A-Za-z]+ [A-Za-z]+$/.test(input)
}

// This is the function that should only receive strings with two words 
function showName(name: FullName) {
    let [first, last] = name.split(' ') // Can be splited with safety
    console.log(`${last}, ${first}`)
}

let name1 = 'John'
let name2 = 'John Doe'
let name3 = 'John Doe Junior'

// Error, you can't assume this string has two words before testing it
showName(name2)

for (let name of [name1, name2, name3]) {
    if (isTwoWords(name)) {
        // No errors, TS knows that only a TwoWords type reach this line
        showName(name)
    }
}
cvsguimaraes
  • 12,910
  • 9
  • 49
  • 73
0

You can ensure your string has two words in it possibly containing letters with accents, multiple hyphens within, multiple apostrophes within, and separated by a space with RegEx.

const allowedChars = "a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; // https://stackoverflow.com/a/1073545/188246
const isTwoWordNameRegEx = new RegExp(`^[${allowedChars}]+(['\-][${allowedChars}]+)* [${allowedChars}]+(['\-][${allowedChars}]+)*$`, "i");

isTwoWordNameRegEx.test("Sébastien Doe");         // true
isTwoWordNameRegEx.test("John Doe");              // true
isTwoWordNameRegEx.test("John Doe-Williams")      // true
isTwoWordNameRegEx.test("Scarlett O'Hara")        // true
isTwoWordNameRegEx.test("John Doe-Williams-Jane") // true
isTwoWordNameRegEx.test("John Doe-")              // false
isTwoWordNameRegEx.test("John Doe'")              // false
isTwoWordNameRegEx.test("John' Doe")              // false
isTwoWordNameRegEx.test("John Doe Williams")      // false

Now that I've mentioned this... don't do it! It's still making an assumption about how a name might be. Please read Falsehoods Programmers Believe About Names.

If you really want to restrict it to two words, then please consider a very relaxed version:

const isTwoWordNameRegEx = /^\S+ \S+$/;
David Sherret
  • 101,669
  • 28
  • 188
  • 178