Convert objective-c typedef to its string equivalent

Viewed 139786

Assuming that I have a typedef declared in my .h file as such:

typedef enum {
  JSON,
  XML,
  Atom,
  RSS
} FormatType;

I would like to build a function that converts the numeric value of the typedef to a string. For example, if the message [self toString:JSON] was sent; it would return 'JSON'.

The function would look something like this:

-(NSString *) toString:(FormatType)formatType {
  //need help here
  return [];
}

Incidentally, if I try this syntax

[self toString:FormatType.JSON];

to pass the typedef value to the method, I get an error. What am I missing?

22 Answers

This is really a C question, not specific to Objective-C (which is a superset of the C language). Enums in C are represented as integers. So you need to write a function that returns a string given an enum value. There are many ways to do this. An array of strings such that the enum value can be used as an index into the array or a map structure (e.g. an NSDictionary) that maps an enum value to a string work, but I find that these approaches are not as clear as a function that makes the conversion explicit (and the array approach, although the classic C way is dangerous if your enum values are not continguous from 0). Something like this would work:

- (NSString*)formatTypeToString:(FormatType)formatType {
    NSString *result = nil;

    switch(formatType) {
        case JSON:
            result = @"JSON";
            break;
        case XML:
            result = @"XML";
            break;
        case Atom:
            result = @"Atom";
            break;
        case RSS:
            result = @"RSS";
            break;
        default:
            [NSException raise:NSGenericException format:@"Unexpected FormatType."];
    }

    return result;
}

Your related question about the correct syntax for an enum value is that you use just the value (e.g. JSON), not the FormatType.JSON sytax. FormatType is a type and the enum values (e.g. JSON, XML, etc.) are values that you can assign to that type.

You can't do it easily. In C and Objective-C, enums are really just glorified integer constants. You'll have to generate a table of names yourself (or with some preprocessor abuse). For example:

// In a header file
typedef enum FormatType {
    JSON,
    XML,
    Atom,
    RSS
} FormatType;

extern NSString * const FormatType_toString[];

// In a source file
// initialize arrays with explicit indices to make sure 
// the string match the enums properly
NSString * const FormatType_toString[] = {
    [JSON] = @"JSON",
    [XML] = @"XML",
    [Atom] = @"Atom",
    [RSS] = @"RSS"
};
...
// To convert enum to string:
NSString *str = FormatType_toString[theEnumValue];

The danger of this approach is that if you ever change the enum, you have to remember to change the array of names. You can solve this problem with some preprocessor abuse, but it's tricky and ugly.

Also note that this assumes you have a valid enum constant. If you have an integer value from an untrusted source, you additionally need to do a check that your constant is valid, e.g. by including a "past max" value in your enum, or by checking if it's less than the array length, sizeof(FormatType_toString) / sizeof(FormatType_toString[0]).

define typedef enum in class header:

typedef enum {
    IngredientType_text  = 0,
    IngredientType_audio = 1,
    IngredientType_video = 2,
    IngredientType_image = 3
} IngredientType;

write a method like this in class:

+ (NSString*)typeStringForType:(IngredientType)_type {
   NSString *key = [NSString stringWithFormat:@"IngredientType_%i", _type];
   return NSLocalizedString(key, nil);
}

have the strings inside Localizable.strings file:

/* IngredientType_text */
"IngredientType_0" = "Text";
/* IngredientType_audio */
"IngredientType_1" = "Audio";
/* IngredientType_video */
"IngredientType_2" = "Video";
/* IngredientType_image */
"IngredientType_3" = "Image";

First of all, with regards to FormatType.JSON: JSON is not a member of FormatType, it's a possible value of the type. FormatType isn't even a composite type — it's a scalar.

Second, the only way to do this is to create a mapping table. The more common way to do this in Objective-C is to create a series of constants referring to your "symbols", so you'd have NSString *FormatTypeJSON = @"JSON" and so on.

Every answer here basically says the same thing, create a regular enum and then use a custom getter to switch between strings.

I employ a much simpler solution that is faster, shorter, and cleaner—using Macros!


#define kNames_allNames ((NSArray <NSString *> *)@[@"Alice", @"Bob", @"Eve"])
#define kNames_alice ((NSString *)kNames_allNames[0])
#define kNames_bob ((NSString *)kNames_allNames[1])
#define kNames_eve ((NSString *)kNames_allNames[2])

Then you can simply start to type kNam... and autocomplete will display the lists you desire!

Additionally, if you want to handle logic for all the names at once you can simply fast enumerate the literal array in order, as follows:

for (NSString *kName in kNames_allNames) {}

Lastly, the NSString casting in the macros ensures behavior similar to typedef!


Enjoy!

I think I just created the most elegant approach to solve this. (inspired by @AdamRosenfield)

For example, you may declare the enum like this:

typedef NS_ENUM(int, MyEnum) {
    MyEnumDefault,
    MyEnumOff,
    MyEnumOn,
    MyEnumMixed,
};

static NSString * const __SGEnumMap_MyEnum[] = {
    [MyEnumDefault] = @"default",
    [MyEnumOff] = @"off",
    [MyEnumOn] = @"on",
    [MyEnumMixed] = @"mixed"
};
    
SGENUM_DEFINE_ToString(MyEnum)
SGENUM_DEFINE_FromString(MyEnum)

Then, you get the two functions to convert between enums and strings:

NSString *result = MyEnumToString(MyEnumOn); 
NSLog(@"%@", result);
// on
    
MyEnum value = MyEnumFromString(@"off", -1); // -1 as the default value
NSLog(@"%d", result);
// 1

Is that cool? Here are the magic macros:

#define SGENUM_DEFINE_ToString(name) NS_INLINE NSString *name##ToString(name value){\
    int count = sizeof(__SGEnumMap_##name) / sizeof(NSString *);\
    if (value > count || value < 0) return nil;\
    return __SGEnumMap_##name[value];\
}

#define SGENUM_DEFINE_FromString(name) NS_INLINE name name##FromString(NSString *input, int defaultValue) {\
    int count = sizeof(__SGEnumMap_##name) / sizeof(NSString *);\
    for (int i = 0; i < count; i++) {\
        NSString *str = __SGEnumMap_##name[i];\
        if ([str isEqual:input]) {\
            return i;\
        }\
    }\
    return defaultValue;\
}

Many answers all fairly good.

If you are after a generic, Objective C solution that uses some macros...

Key feature is it uses the enum as an index into a static array of NSString constants. the array itself is wrapped into a function to make it more like the suite of NSStringFromXXX functions prevalent in the Apple APIs.

you will need to #import "NSStringFromEnum.h" found here http://pastebin.com/u83RR3Vk

[EDIT] also needs #import "SW+Variadic.h" found here http://pastebin.com/UEqTzYLf

Example 1 : completely define a NEW enum typedef, with string converters.

in myfile.h


 #import "NSStringFromEnum.h"

 #define define_Dispatch_chain_cmd(enum)\
 enum(chain_done,=0)\
 enum(chain_entry)\
 enum(chain_bg)\
 enum(chain_mt)\
 enum(chain_alt)\
 enum(chain_for_c)\
 enum(chain_while)\
 enum(chain_continue_for)\
 enum(chain_continue_while)\
 enum(chain_break_for)\
 enum(chain_break_while)\
 enum(chain_previous)\
 enum(chain_if)\
 enum(chain_else)\


interface_NSString_Enum_DefinitionAndConverters(Dispatch_chain_cmd)

in myfile.m:


 #import "myfile.h"

 implementation_NSString_Enum_Converters(Dispatch_chain_cmd)

to use :

NSString *NSStringFromEnumDispatch_chain_cmd(enum Dispatch_chain_cmd value);

NSStringFromEnumDispatch_chain_cmd(chain_for_c) returns @"chain_for_c"

  enum Dispatch_chain_cmd enumDispatch_chain_cmdFromNSString(NSString *value);

enumDispatch_chain_cmdFromNSString(@"chain_previous") returns chain_previous

Example 2: provide conversion routines for an existing enum also demonstrates using a settings string, and renaming the typename used in the functions.

in myfile.h


 #import "NSStringFromEnum.h"


 #define CAEdgeAntialiasingMask_SETTINGS_PARAMS CAEdgeAntialiasingMask,mask,EdgeMask,edgeMask

 interface_NSString_Enum_Converters(CAEdgeAntialiasingMask_SETTINGS_PARAMS)

in myfile.m:


 // we can put this in the .m file as we are not defining a typedef, just the strings.
 #define define_CAEdgeAntialiasingMask(enum)\
 enum(kCALayerLeftEdge)\
 enum(kCALayerRightEdge)\
 enum(kCALayerBottomEdge)\
 enum(kCALayerTopEdge)



 implementation_NSString_Enum_Converters(CAEdgeAntialiasingMask_SETTINGS_PARAMS)
Related