Code Review Videos > C# > C# Kebab Case To Camel Case

C# Kebab Case To Camel Case

In this post I’m going to cover how to convert from kebab case to camel case when using C#.

The need for converting from kebab case to camel case is a common coding puzzle challenge, and the basis for this post is no different. I have been working through Exercism’s C# track and am on the Squeaky Clean exercise.

The specific test I have had to solve is as follows:

using Xunit;
using Exercism.Tests;

public class SqueakyCleanTests
{
    [Fact]
    [Task(3)]
    public void Convert_kebab_to_camel_case()
    {
        Assert.Equal("àḂç", Identifier.Clean("à-ḃç"));
    }
}Code language: C# (cs)

And the tricky part, at least at first glance, is converting characters that are not the standard English alphabet.

The High Level Idea

Let’s look at some pseudo code for converting from kebab-case to camelCase:

function convertKebabToCamel(kebabCaseString) {

    words = split kebabCaseString by '-'
    output = words[0]

    for x = 1 to length of words {

        capitaliseTheFirstLetterOf(words[x])

        append words[x] to output

    }

    return output
}Code language: plaintext (plaintext)

Or, to put that into English, we start by splitting the entire string on the hyphen character (the kebab stick).

We don’t want to capitalise the first word, so that remains untouched. The first words becomes the basis of our output string.

Then we walk through the remaining words, capitalising the first letter of the word and appending it to the output string.

Once all the words are processed we return the new string.

A First Attempt In C#

The test at the start of this post indicates this is Task(3) – the third little sub task of the wider code puzzle. The aim is to solve the tests by writing the best implementation you are capable of, such that all failing tests begin to pass, and the task is complete.

Here’s my first attempt:

private static string ConvertKebabToCamel(string id)
{
    var stringArray = id.Split('-');
    var output = new StringBuilder(stringArray[0]);

    for (var i = 1; i < stringArray.Length; i++)
    {
        output.Append(char.ToUpper(stringArray[i][0]) + stringArray[i].Substring(1));
    }

    return output.ToString();
}
Code language: C# (cs)

Just like the high level idea above, we start by splitting the string on the hyphen character.

That gives us an array of strings – string[].

On line 4 we create a new instance of the StringBuilder. This will allow us to … err, build up a string in an object oriented fashion, starting with that first string, or everything that comes before the first hyphen.

I really don’t like for loops, but it’s the simplest thing I know how to do to get the idea down.

A variation on the usual theme, in this for loop we start at index 1, rather than 0. We took the first element already.

Inside the for loop is really “where the magic happens”.

Creating The Next Word

Coming from JavaScript / TypeScript, there is no distinct datatype for individual characters. A single character is a string with a single character. Or a string with a length of one.

It’s the same concept, as such, in C#, only individual characters do have their own primitive type, distinct from strings: the char.

char.ToUpper(stringArray[i][0])Code language: C# (cs)

There’s quite a lot happening here.

First we have stringArray[i] which as we covered above, has i starting at index 1.

That means on the first iteration, this would be stringArray[1], or the second word in our original string.

We then get the first character of that string by indexing into the string at index 0:

stringArray[1][0]Code language: C# (cs)

As we now have an individual character, we can then use the static method char.ToUpper to turn that lower case character into its upper case equivalent.

From there we use the + sign to concatenate the newly upper-cased character with the rest of the contents of the current stringArray[i]. Crucially here, we do not want the first character from that string, so we use Substring to get every character starting from position (or index) 1.

Can We Refactor?

The given implementation does make the specific test pass:

c# convert kebab case to camel case exercism test outcome

However getting the tests to pass is just the first step.

If we follow the typical TDD workflow of Red-Green-Refactor, we are now looking at ways we can either improve our original implementation.

Rider is particularly good at suggesting some smart refactorings:

c# Use range indexer refactoring

If we accept this refactoring we get:

stringArray[i][1..]);Code language: C# (cs)

In terms of functionality, both expressions achieve the same outcome by extracting a substring starting from index 1 until the end of the string.

If you’re using C# 8.0 or a later version, you can use the stringArray[i][1..] range operator syntax.

Otherwise, you can use the stringArray[i].Substring(1) method for older versions of C#.

Can We Use foreach?

I’m looking at that for loop as my next big thing I’d love to remove. The tricky part is that the index starts at 1, so simply replacing like for like is not super easy for a newbie to C#.

One possible way to do this is to use Linq.

That does mean adding another dependency, but this would be a dependency on the Microsoft’s built in “stuff” so I think that’s OK?

using System.Linq;

// other stuff

foreach (var word in stringArray.Skip(1))
{
    newIdentifier.Append(char.ToUpper(word[0]) + word[1..]);
}Code language: C# (cs)

This works because string[] implements IEnumerable<T>, which has a bunch of very handy methods for working with collections such as Skip, Take, Where, etc.

Personally I’ve found some of the third party books on C# explain IEnumerable in a far more accessible manner than the MS docs on this one.

Remove The Concatenation

I’m also not a massive fan of joining strings with plus signs. It feels wrong to me.

A better way, in my opinion, is to use a template string. That’s how I’d do this in JavaScript, and C# has the same concept:

newIdentifier.Append($"{char.ToUpper(word[0])}{word[1..]}");Code language: C# (cs)

Which still passes the test, and so, to me, is a win.

The Code So Far

I’ve not yet completed this code puzzle.

I like to do these challenges every day, but it doesn’t mean I complete them all in one sitting. Sometimes I do – if they are easy or ones I have done before, or something very similar.

Most of the time it takes me ages to complete them.

But that’s fine, because I learn a lot as I go. I’ve often thought about documenting my progress, but never really came up with a good approach to doing that … until now.

I feel like calling out just a small part of a larger puzzle is quite interesting.

Anyway, here’s the code I have so far:

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;

public static class Identifier
{
    public static string Clean(string identifier)
    {
        var patternActions = new Dictionary<string, Func<string, string>>
        {
            { @"\s", id => id.Replace(" ", "_") },
            { "\0", id => id.Replace("\0", "CTRL") },
            { "-", ConvertKebabToCamel },
        };

        foreach (var patternAction in patternActions)
        {
            if (Regex.IsMatch(identifier, patternAction.Key))
            {
                identifier = patternAction.Value(identifier);
            }
        }

        return identifier;
    }

    private static string ConvertKebabToCamel(string id)
    {
        var stringArray = id.Split('-');
        var newIdentifier = new StringBuilder(stringArray[0]);

        foreach (var word in stringArray.Skip(1))
        {
            newIdentifier.Append($"{char.ToUpper(word[0])}{word[1..]}");
        }

        return newIdentifier.ToString();
    }
}Code language: C# (cs)

I can’t say I’m 100% happy with it. I feel there is remaining room for improvement for sure, but that’s all part of the fun.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.