nsjsonserialization对普通旧对象?

发布于 2025-01-24 13:33:01 字数 374 浏览 2 评论 0原文

许多现代编程语言都有JSON库,这些库支持编码和解码JSON to/from从“普通旧对象” - IE类实例的类实例(主要具有数据属性)(属性可以是可以琐碎的/编码的类型,也可以是其他普通的类型旧对象)。示例包括Google的GSON,Golang的编码/JSON等。

有类似于Objective-C的东西吗?

我知道可以枚举Objective-C类的属性,而且似乎有人会使用该功能来创建JSON“ Bean Mapper”,但是Google搜索对我没有任何结果,除了

*)基本上是理由是,不需要编写大量的样板(其示例实现为36 LOC,可以解析3属性)并不是一个重大改进,并且构建了几个可选的回调以允许数据验证。我显然不同意所有这些。

A lot of modern programming languages have JSON libraries that support encoding and decoding json to/from "plain old objects" - i.e. instances of classes that primarily just have data properties (properties can either be types that can be trivially de/encoded or other plain old objects). Examples include Google's GSON, golang's encoding/json and others.

Is there something similar to Objective-C?

I know that it is possible to enumerate properties for Objective-C classes, and it seems reasonable that someone would have used that capability to create a JSON "bean mapper", but Google searching yielded no results for me, except this blog post on Apple's Swift website showing how to manually deserialize JSON to "model objects" and why they think that doing this automatically (DRYing the code) is a bad idea (*).

*) The reasoning is basically, that not needing to write a lot of boilerplate (their sample implementation is 36 LoC to parse 3 properties) is not a significant improvement and building a couple of optional callbacks to allow data validation is hard. I obviously disagree with all of this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你是暖光i 2025-01-31 13:33:01

这是我的解决方案,它不是基于库(我找不到的),而是使用基础和客观-C运行时方法 - 如上所述:

#import <objc/runtime.h>

NSArray<NSString*>* classPropertyList(id instance) {
    NSMutableArray* propList = [NSMutableArray array];
    unsigned int numProps = 0;
    objc_property_t* props = class_copyPropertyList(object_getClass(instance), &numProps);
    for (int i = 0; i < numProps; i++)
        [propList addObject:[NSString stringWithUTF8String:property_getName(props[i])]];
    free(props);
    return propList;
}

NSString* typeOfProperty(Class clazz, NSString* propertyName) {
    objc_property_t prop = class_getProperty(clazz, [propertyName UTF8String]);
    NSArray<NSString*>* propAttrs = [[NSString stringWithUTF8String:property_getAttributes(prop)] componentsSeparatedByString:@","];
    if ([(propAttrs[0]) hasPrefix:@"T@\""])
        return [propAttrs[0] componentsSeparatedByString:@"\""][1];
    return nil;
}

@implementation JSONMarshallable

- (NSData*)toJSON {
    return [self toJSON:self withNullValues:YES];
}

- (NSString*)toJSONString {
    return [self toJSONString:self withNullValues:YES];
}

- (NSData*)toJSON:_ withNullValues:(bool)nullables {
    NSError* error;
    NSDictionary* dic = [self toDictionary:self withNullValues:nullables];
    NSData* json = [NSJSONSerialization dataWithJSONObject:dic options:0 error:&error];
    if (!json) {
        NSLog(@"Error encoding DeviceConfigurationRequest: %@", error);
        return nil;
    }
    return json;
}

- (NSString*) toJSONString:_ withNullValues:(bool)nullables {
    NSData* json = [self toJSON:self withNullValues:nullables];
    return [[NSString alloc] initWithBytes:[json bytes] length:[json length] encoding:NSUTF8StringEncoding];
}

- (NSDictionary*)toDictionary:_ withNullValues:(bool)nullables {
    NSMutableDictionary* dic = [NSMutableDictionary new];
    for (id propName in classPropertyList(self)) {
        id val = [self valueForKey:propName];
        if (!nullables && (val == nil || val == NSNull.null))
            continue;
        if ([val respondsToSelector:@selector(toDictionary:withNullValues:)])
            val = [val toDictionary:val withNullValues:nullables];
        [dic setObject:(val == nil ? NSNull.null : val) forKey:propName];
    }
    return dic;
}

- (instancetype)initWithJSONString:(NSString*)json {
    return [self initWithJSON:[json dataUsingEncoding:NSUTF8StringEncoding]];
}

- (instancetype)initWithJSON:(NSData*)json {
    NSError* error;
    if (json == nil)
        return nil;
    NSDictionary* dataValues = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
    if (!dataValues) {
        NSLog(@"Error parsing invalid JSON for %@: %@", NSStringFromClass(object_getClass(self)), error);
        return nil;
    }
    return [self initWithDictionary:dataValues];
}

- (instancetype)initWithDictionary:(NSDictionary*)dataValues {
    if (dataValues == nil)
        return nil;
    if (self = [super init])
        for (id key in dataValues) {
            id val = [dataValues objectForKey:key];
            if (![self respondsToSelector:NSSelectorFromString(key)])
                continue;
            NSString* typeName = typeOfProperty([self class], key);
            if ([val isKindOfClass:[NSNull class]]) { // translate NSNull values to something useful, if we can
                if (typeName == nil)
                    continue; // don't try to set nil to non-pointer fields
                val = nil;
            } else if ([val isKindOfClass:[NSDictionary class]] && typeName != nil)
                val = [[NSClassFromString(typeName) alloc] initWithDictionary:val];
            [self setValue:val forKey:key];
        }
    return self;
}

@end

然后很容易通过通过从继承JSONMARSHALLABLE,例如:

model.h

#import "JSONMarshallable.h"

@interface MyModel : JSONMarshallable

@property NSString* stringValue;
@property NSNumber* numericValue;
@property bool boolValue;

@end

model.m

@implementation MyModel
@end

somets of sosings elselse.m

// ...

NSData* someJson;
MyModel* obj = [[MyModel alloc] initWithJSON:someJson];
NSString* jsonObj = [obj toJSONString:nil withNullValues:NO];

欢迎批评家呢(我不是很擅长目标C,并且可能使很多人造PAS

Here is my solution, which is not based on a library - as I couldn't find any - but instead using the Foundation and Objective-C runtime methods - as discussed in the comments above:

#import <objc/runtime.h>

NSArray<NSString*>* classPropertyList(id instance) {
    NSMutableArray* propList = [NSMutableArray array];
    unsigned int numProps = 0;
    objc_property_t* props = class_copyPropertyList(object_getClass(instance), &numProps);
    for (int i = 0; i < numProps; i++)
        [propList addObject:[NSString stringWithUTF8String:property_getName(props[i])]];
    free(props);
    return propList;
}

NSString* typeOfProperty(Class clazz, NSString* propertyName) {
    objc_property_t prop = class_getProperty(clazz, [propertyName UTF8String]);
    NSArray<NSString*>* propAttrs = [[NSString stringWithUTF8String:property_getAttributes(prop)] componentsSeparatedByString:@","];
    if ([(propAttrs[0]) hasPrefix:@"T@\""])
        return [propAttrs[0] componentsSeparatedByString:@"\""][1];
    return nil;
}

@implementation JSONMarshallable

- (NSData*)toJSON {
    return [self toJSON:self withNullValues:YES];
}

- (NSString*)toJSONString {
    return [self toJSONString:self withNullValues:YES];
}

- (NSData*)toJSON:_ withNullValues:(bool)nullables {
    NSError* error;
    NSDictionary* dic = [self toDictionary:self withNullValues:nullables];
    NSData* json = [NSJSONSerialization dataWithJSONObject:dic options:0 error:&error];
    if (!json) {
        NSLog(@"Error encoding DeviceConfigurationRequest: %@", error);
        return nil;
    }
    return json;
}

- (NSString*) toJSONString:_ withNullValues:(bool)nullables {
    NSData* json = [self toJSON:self withNullValues:nullables];
    return [[NSString alloc] initWithBytes:[json bytes] length:[json length] encoding:NSUTF8StringEncoding];
}

- (NSDictionary*)toDictionary:_ withNullValues:(bool)nullables {
    NSMutableDictionary* dic = [NSMutableDictionary new];
    for (id propName in classPropertyList(self)) {
        id val = [self valueForKey:propName];
        if (!nullables && (val == nil || val == NSNull.null))
            continue;
        if ([val respondsToSelector:@selector(toDictionary:withNullValues:)])
            val = [val toDictionary:val withNullValues:nullables];
        [dic setObject:(val == nil ? NSNull.null : val) forKey:propName];
    }
    return dic;
}

- (instancetype)initWithJSONString:(NSString*)json {
    return [self initWithJSON:[json dataUsingEncoding:NSUTF8StringEncoding]];
}

- (instancetype)initWithJSON:(NSData*)json {
    NSError* error;
    if (json == nil)
        return nil;
    NSDictionary* dataValues = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
    if (!dataValues) {
        NSLog(@"Error parsing invalid JSON for %@: %@", NSStringFromClass(object_getClass(self)), error);
        return nil;
    }
    return [self initWithDictionary:dataValues];
}

- (instancetype)initWithDictionary:(NSDictionary*)dataValues {
    if (dataValues == nil)
        return nil;
    if (self = [super init])
        for (id key in dataValues) {
            id val = [dataValues objectForKey:key];
            if (![self respondsToSelector:NSSelectorFromString(key)])
                continue;
            NSString* typeName = typeOfProperty([self class], key);
            if ([val isKindOfClass:[NSNull class]]) { // translate NSNull values to something useful, if we can
                if (typeName == nil)
                    continue; // don't try to set nil to non-pointer fields
                val = nil;
            } else if ([val isKindOfClass:[NSDictionary class]] && typeName != nil)
                val = [[NSClassFromString(typeName) alloc] initWithDictionary:val];
            [self setValue:val forKey:key];
        }
    return self;
}

@end

It is then easy to create custom model objects by inheriting from JSONMarshallable, like so:

model.h:

#import "JSONMarshallable.h"

@interface MyModel : JSONMarshallable

@property NSString* stringValue;
@property NSNumber* numericValue;
@property bool boolValue;

@end

model.m:

@implementation MyModel
@end

SomeThingElse.m:

// ...

NSData* someJson;
MyModel* obj = [[MyModel alloc] initWithJSON:someJson];
NSString* jsonObj = [obj toJSONString:nil withNullValues:NO];

Critics are welcome! (I'm not very good at Objective C and probably made a lot of faux pas ????)

Issues:

  • I can handle nullable numbers with NSNumber* (though C primitives work fine for non-nullable numbers), but I don't know how to represent nullable booleans - i.e. a field that is optional and not encoded when using withNullValues:NO.
  • Sending fields for which there are no properties (for example, the server I work with sends values in both snake-case and underscrore-case to make it easy to parse) throws exception. (solved by using respondsToSelector: and setValue: instead of setValuesForKeysWithDictionary:).
  • Trying to set nil values to primitive-typed fields causes exceptions. (solved by checking for property type and NSNull).
  • Doesn't work at all for nesting objects - i.e. a custom model object with properties that are also custom model objects. (solved by checking for property types and recursing encoding/decoding).
  • Probably doesn't handle arrays well - I have yet to need those in my software, so I haven't implemented proper support (though I verified that encoding simple string arrays works well).
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文