Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore nil properties in toJsonString() #70

Closed
rafcabezas opened this issue Apr 12, 2016 · 17 comments
Closed

Ignore nil properties in toJsonString() #70

rafcabezas opened this issue Apr 12, 2016 · 17 comments

Comments

@rafcabezas
Copy link
Contributor

Is there any way to set up EVReflection to ignore properties that are nil when generating JSON?

eg. I have a model

class Test : EVObject {
  var prop1 : String? = nil
  var prop2 : String? = nil
}

if prop1 is nil, I would like toJsonString() to return a string ignoring prop1 completely. (This is consistent with the way JAYL-Entity works, and I'm in the process of replacing it with EVReflection).

Thanks!,

Raf.

@rafcabezas
Copy link
Contributor Author

I ended up writing an extension that does what I needed. Any feedback? If you'd like I can do a pull request for this (without the debugging statements).

Thanks!,

extension EVObject {
    func toJsonStringWithoutEmptyKeys(performKeyCleanup: Bool) -> String {

        let dict = EVReflection.toDictionary(self, performKeyCleanup: performKeyCleanup).0
        let cleanDict = NSMutableDictionary()

        for (key, value) in dict {
            if let key = key as? String {
                if let value = value as? String where value.characters.count == 0 {
                    print("Ignoring \(key)")
                    continue
                }
                else if let value = value as? NSArray where value.count == 0 {
                    print("Ignoring \(key)")
                    continue
                }
                else if value is NSNull {
                    print("Ignoring \(key)")
                    continue
                }
                else {
                    cleanDict[key] = value
                }
            }
        }

        //Get json string from element dictionary
        do {
            let jsonData = try NSJSONSerialization.dataWithJSONObject(cleanDict, options: NSJSONWritingOptions.PrettyPrinted)
            return String(data: jsonData, encoding: NSASCIIStringEncoding) ?? ""
        } catch let error as NSError {
            print(error)
            return ""
        }

    }
}

@evermeer
Copy link
Owner

This will work if you do not have any sub objects. I was thinking of adding an extra parameter to the toDictionary function which will do about the same. I first have 2 annoying bugs to solve. I will pick this up after that.

@rafcabezas
Copy link
Contributor Author

No worries, and thanks for your work, Edwin!

@evermeer
Copy link
Owner

I just pushed version 2.26.0 to GitHub and Cocoapods with support for ignoring certain values.

See the test at:

Here is the code:

    func testTypeDict() {
        let a = TestObjectSkipValues()
        a.value1 = "test1"
        a.value2 = ""
        a.value4 = 4
        a.value6 = [String]()
        a.value6?.append("arrayElement")
        a.value7 = [String]()
        let json = a.toJsonString()
        print("json = \(json)")
    }
}

class TestObjectSkipValues: EVObject {
    var value1: String?
    var value2: String?
    var value3: String?
    var value4: Int?
    var value5: Int?
    var value6: [String]?
    var value7: [String]?
    var value8: [String]?

    // Put this in your own base class if you want to have this logic in all your classes
    override func skipPropertyValue(value: Any, key: String) -> Bool {
        if let value = value as? String where value.characters.count == 0 {
            print("Ignoring empty string for key \(key)")
            return true
        } else if let value = value as? NSArray where value.count == 0 {
            print("Ignoring empty NSArray for key\(key)")
            return true
        } else if value is NSNull {
            print("Ignoring NSNull for key \(key)")
            return true
        }
        return false
    }
}

You will get this in your output window for this test:

Test Case '-[EVReflection_iOS_Tests.EVReflectionSkipValueTest testTypeDict]' started.
Ignoring empty string for key value2
Ignoring NSNull for key value3
Ignoring NSNull for key value5
Ignoring empty NSArray for keyvalue7
Ignoring NSNull for key value8
json = {
  "value4" : 4,
  "value1" : "test1",
  "value6" : [
    "arrayElement"
  ]
}
Test Case '-[EVReflection_iOS_Tests.EVReflectionSkipValueTest testTypeDict]' passed (0.005 seconds).

@rafcabezas
Copy link
Contributor Author

Nice!, thank you Edwin. I'll check it out right now.

@rafcabezas
Copy link
Contributor Author

rafcabezas commented Apr 27, 2016

Somehow adding this to my BaseClass:EVObject, is messing up deserialization of classes that have arrays of other BaseClass derivatives.
Is there anyway to just do this when generating the output json, and not during deserialization?

Apparently, the
else if value is NSNull
is the offensive bit. But if I comment it out, I end up with strings like this in the output JSON:
"weeks_pattern" : null,

Thanks

@evermeer
Copy link
Owner

ah, true, deserialization is also effected. I think I know how to fix this. Let me work on it..

@evermeer evermeer reopened this Apr 27, 2016
@rafcabezas
Copy link
Contributor Author

Thanks Edwin!

@evermeer
Copy link
Owner

OK, Almost there... Now every function has an extra parameter (or replaced the performKeyCleanup) where you can specify the options from:

public struct ConvertionOptions: OptionSetType {
    public let rawValue: Int
    public init(rawValue: Int) { self.rawValue = rawValue }

    public static let None = ConvertionOptions(rawValue: 0)
    public static let PropertyConverter = ConvertionOptions(rawValue: 1)
    public static let PropertyMapping = ConvertionOptions(rawValue: 2)
    public static let SkipPropertyValue = ConvertionOptions(rawValue: 4)
    public static let KeyCleanup = ConvertionOptions(rawValue: 8)

    public static let Default: ConvertionOptions = [PropertyConverter, PropertyMapping, SkipPropertyValue, KeyCleanup]
}

Most functions have the default set to .Default. The NSCoding (plus save and load) have it set to .None

I still need to add a lot of unit tests to capture all variations. I think I need an other day to make it solid.

@rafcabezas
Copy link
Contributor Author

Cool, thanks!
BTW, Is there a reason the options couldn't be an enum?
So, with this change, I'll basically be able to call toJsonString() and pass a SkipPropertyValue option, where performKeyCleanup would be called during serialization?

Thanks!!

@evermeer
Copy link
Owner

evermeer commented Apr 28, 2016

It's an optionset which is better in this case. You Will be able to pass on multiple options in an array notation. See the Default item. It has all options. In your case i was enumerating properties after a cleanup which lead to skipping values from the json. I am now already calling that function with the right options. So the skipvalues function will always be skipped during enumeration, but the actual set can be influenced using this new parameter. Its a little hard to explain without code. You will see the unit tests soon. :-)

@evermeer
Copy link
Owner

I have pushed the new code to github. It's not a new version yet.
I think this would work for you. After some more tests I will push this as a new version.
See the unit test at:

@rafcabezas
Copy link
Contributor Author

Cool! Will try in a sec.

@rafcabezas
Copy link
Contributor Author

Nice work, it's working perfectly!!

@evermeer
Copy link
Owner

And it's now pushed as version 2.27.0 to GitHub and CocoaPods

@evermeer
Copy link
Owner

Some aditional info (now also in the readme)

Almost any EVReflection functions have a property for ConversionOptions. In most cases the default value of this is set to .Default. In case of NSCoding and related functions (like save and load) the default is set to .None. The available options are:

  • PropertyConverter - if true then the propertyConverters function on the object will be called.
  • PropertyMapping - if true then the propertyMapping function on the object will be called.
  • SkipPropertyValue - if true then the skipPropertyValue function on the object will be called.
  • KeyCleanup - If true then the keys will be cleaned up (like pascal case and snake case conversion)

You can use multiple options at the same type by specifying them in array notation. Like the .Default will be all options enabled like: [PropertyConverter, PropertyMapping, SkipPropertyValue, KeyCleanup]

@rafcabezas
Copy link
Contributor Author

Thank you! I was actually just reading the updates to the readme :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants