Open dvdsgl opened 6 years ago
It seems that exceptions may be out of fashion in Objective-C as well...
QTTopLevelFromData
functions should catch exceptions and write to the NSError **error
so there's only one style of error handling there.
I noticed that you can annotate a property as nonnull
by adding the property to the list of required
fields in a schema. Is this nullability validated in any way?
Ideally it would be considered an error if one attempted to parse a model object with a JSON payload whose required
property is missing or null
.
I agree that exceptions are "out of fashion". It's more common to pass a pointer to an NSError
and optionally have it populated if the method in question returns NO
or nil
(depending on context).
Defining an enum
on a property of type string
does not seem to generate an enum in the generated model (even when the appropriate checkbox is checked).
@n8chur because of our recursive marshaling of dynamic values, I think threading the NSError
in and out of our property initializers is going to be way too cumbersome. My plan is to still use exceptions internally but to catch them at the top level and convert them to NSError
.
@n8chur nonnull
is validated in the NSDictionary objectForKey:withClass:
method--this is also the source of the exceptions design choice.
Our dynamic checking is far from comprehensive, though. For example, collections of boxed primitives (e.g. NSArray<NSNumber *> *
) do not get their elements checked.
@n8chur enum
is still on our todo list (above), we just need to decide how to implement it. What would you expect/desire?
@dvdsgl It would be great to have properties with typed enums generated something like:
typedef NS_ENUM(NSInteger, SomeEnum){
SomeEnumValueA,
SomeEnumValueB
};
Then the property might look like:
@property (readonly, nonatomic) SomeEnum enumValue;
All of the translation between NSString
and the enum values could be under the hood and the strings themselves do not necessarily need to be exposed publicly.
@n8chur excellent, that's exactly what we had in mind. I should get it in the next couple of days, but feel free to send a PR if you're feeling ambitious 😄 We could really use some passionate contributors with more specialized per-language knowledge.
@n8chur alright, speedbump – what would you expect to happen when enums
appear as values in JSON arrays and maps? Would you expect an NSArray<NSNumber *>
? That seems a bit... tragic. Here's a sample and where we get into trouble.
@dvdsgl I'm currently working on open sourcing a code/test fixture generation tool at Automatic but I may dive in after that's wrapped up!
@dvdsgl Regarding enums: NSArray<NSNumber *> *
seems reasonable, but perhaps with a comment about what that number is.
Another thing to consider is to use a box class for enum values when they need to be contained in NSArray
s/NSDictionary
s.
Something like:
@interface QTWeaknessBox : NSObject
@property (readonly, nonatomic) QTWeakness value;
@end
...
@property (nonatomic, readonly, copy) NSArray<QTWeaknessBox *> *weaknesses;
@n8chur alright... Have a look: https://app.quicktype.io/#l=objc&s=pokedex
I had to make 'pseudo-enums' for reference semantics (boxing) and to preserve readable type names when enums become type parameters (e.g. I want NSArray<QTWeakness *>
not NSArray<NSNumber *>
):
@interface QTWeakness : NSObject
@property (nonatomic, readonly, copy) NSString *value;
+ (instancetype _Nullable)withValue:(NSString *)value;
+ (QTWeakness *)bug;
+ (QTWeakness *)dark;
+ (QTWeakness *)dragon;
+ (QTWeakness *)electric;
@end
@implementation QTWeakness
static NSMutableDictionary<NSString *, QTWeakness *> *qtWeaknesses;
@synthesize value;
+ (QTWeakness *)bug { return qtWeaknesses[@"Bug"]; }
+ (QTWeakness *)dark { return qtWeaknesses[@"Dark"]; }
+ (QTWeakness *)dragon { return qtWeaknesses[@"Dragon"]; }
+ (QTWeakness *)electric { return qtWeaknesses[@"Electric"]; }
+ (void)initialize
{
NSArray<NSString *> *values = @[
@"Bug",
@"Dark",
@"Dragon",
@"Electric",
];
qtWeaknesses = [NSMutableDictionary dictionaryWithCapacity:values.count];
for (NSString *value in values) qtWeaknesses[value] = [[QTWeakness alloc] initWithValue:value];
}
+ (instancetype _Nullable)withValue:(NSString *)value { return qtWeaknesses[value]; }
- (instancetype)initWithValue:(NSString *)val
{
if (self = [super init]) value = val;
return self;
}
- (NSUInteger)hash { return value.hash; }
@end
Perhaps this is a tad unorthodox, but I think it's the best for now, on balance. It's also pretty nice for autocompletion and you can write expressions like pikachu.weakness == QTWeakness.water
.
You can see in the code that I track the set of these pseudo-enums, which means they can be differentiated from natural NSUInteger
enums. It makes sense to emit natural enums if they aren't used within generic types, so this leaves room for improvement. In the pokedex.json
sample, QTPokemon.egg
could be emitted as a natural enum, for example.
Reading about Mantle, a popular JSON framework for Objective-C, I spotted some good improvements for our Objective-C output:
BOOL
,NSInteger
, anddouble
propertiesNSCopying
andNSCoding
readonly
,strong
, andcopy
property attributeshash
andisEqualTo
NSURL
andNSDate