1

My application code gets JSON data from a server, converts it to a Dictionary and then uses that to hydrate and attempt to save a RealmSwift object with a matching schema.

I ran into a crash caused by a Float value coming into a field that was declared as Int in the model. RLMException was thrown.

As suggested in this thread, one doesn't simply try to catch RLMException.

My question is, is that answer correct? And, if so, what is the correct way to prevent a crash when an unexpected value is found? Isn't there some sort of validation mechanism that I can run on all of the values before attempting to set them?

Community
  • 1
  • 1
0x6A75616E
  • 4,696
  • 2
  • 33
  • 57
  • Why don't you cast that Float to Int? – tbilopavlovic Jun 16 '16 at 07:43
  • Fixing this specific error is trivial, once you know about it. However, what if the server unexpectedly sends a string when an Int was expected? Why should the app crash if that happens? I want to skip or default the field to something else if that happens. – 0x6A75616E Jun 16 '16 at 07:46

3 Answers3

3

You can use third party frameworks for mapping JSON to Realm objects, like ObjectMapper and use it's failable initializer to validate your JSON.

This failable initializer can be used for JSON validation prior to object serialization. Returning nil within the function will prevent the mapping from occuring. You can inspect the JSON stored within the Map object to do your validation.

Dmitry
  • 7,300
  • 6
  • 32
  • 55
0

You can use this to see if everything is ok with json parameters

static func createFromJSONDictionary(json: JSONDictionary) throws -> MyObject {
        guard let
                property1 = json["property1"] as? Int,
                property2 = json["property1"] as? String else {
                   // Throw error.... or create with default values
                   throw NSError(domain: "", code: 0, userInfo: nil)
        }
       // Everything is fine, create object and return it
       let object = MyObject()
       object.something = property1
       return object
    }
tbilopavlovic
  • 1,096
  • 7
  • 13
  • Right. However this forces me to reiterate my schema in yet another place, which I believe it's not optimal/elegant. The Model declaration already tells Realm what type each field should be, why would it be necessary to maintain a list of acceptable types in the validation code as well? I mean, I could create something more elaborate than this using https://realm.io/docs/swift/1.0.1/api/Classes/ObjectSchema.html, which gives me access to the properties and types. But I'm guessing there's a better workflow that I'm missing. – 0x6A75616E Jun 16 '16 at 08:23
0

I ended up writing an extension to handle this. At the moment I'm only validating numbers and dates, but the switch statement could check every single property type easily.

extension Object {

    public convenience init( withDictionary rawData: [String:AnyObject] ) {

        var sanitizedData = rawData

        let dynamicSelf = self.dynamicType

        let schema = dynamicSelf.sharedSchema()


        for property in schema.properties {

            guard let value = sanitizedData[property.name] else { continue }

            var isValid = true

            switch property.type {

                case .Double:

                    let val = Double(String(value))

                    if val == nil || val!.isNaN {
                        isValid = false
                    }

                case .Float:

                    let val = Float(String(value))

                    if val == nil || val!.isNaN {
                        isValid = false
                    }

                case .Int:

                    if Int(String(value)) == nil {
                        isValid = false
                    }

                case .Date:

                    if Int64(String(value)) == nil {

                        isValid = false

                    } else {

                        sanitizedData[property.name] = NSDate(timeIntervalSince1970: value.doubleValue / 1000)

                    }

                default:
                    break

            }


            if !isValid {

                log.error( "Found invalid value: \(value) for property \(property.name) of \(dynamicSelf)" )

                sanitizedData.removeValueForKey(property.name)

            }

        }

        self.init( value: sanitizedData )

    }

}
0x6A75616E
  • 4,696
  • 2
  • 33
  • 57