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

Fatal error ("Optional is only JSONEncodable if Wrapped is") when trying to update SqlNormalizedCache manually after a successful mutation operation #1305

Closed
nateirwin opened this issue Jul 8, 2020 · 19 comments
Labels
Milestone

Comments

@nateirwin
Copy link

While using Apollo iOS 0.29.1, I appear to be running into an issue similar to the one reported here. I asked for help with this issue in Spectrum, and @designatednerd asked me to create a new issue.

I'm trying to update the SqlNormalizedCache (which is setup and working properly) using the result of a successful mutation operation:

apollo.perform(mutation: InsertTaskResponseMutation(taskId: 1, response: "")) { result in
    guard
        let resultData = try? result.get().data,
        let taskResponseDetails = resultData.insertTaskResponses?.returning.first?.fragments.taskResponseDetails
    else {
        return
    }

    apollo.store.withinReadWriteTransaction({ transaction in
        try! transaction.write(object: taskResponseDetails, withKey: "task_responses-\(taskResponseDetails.id)")
    })
}

This is throwing a fatal error in "JSONStandardTypeConversions.swift" at line 109:

Screen Shot 2020-07-08 at 12 01 31 PM

Printing the description of self:

Printing description of self:
▿ Optional<Any>
  - some : 07/08/2020

In this case, self is a property on the response property, which is a custom jsonb type. Here's the type alias:

public typealias jsonb = [String : Any?]

extension Dictionary: JSONDecodable {
    public init(jsonValue value: JSONValue) throws {
        guard let dictionary = value as? Dictionary else {
            throw JSONDecodingError.couldNotConvert(value: value, to: Dictionary.self)
        }
        
        self = dictionary
    }
}

And here's the query:

query ChallengeTaskResponse($taskResponseId: Int) {
    challenge_responses(where: {id: {_eq: $taskResponseId}}) {
        ...ChallengeTaskResponseDetails
    }
}

The mutation:

mutation InsertTaskResponse($response: jsonb) {
    insert_task_responses(objects: {response: $response}) {
        returning {
            ...TaskResponseDetails
        }
    }
}

And, finally, the fragment that's used by both the query and the mutation:

fragment TaskResponseDetails on task_responses {
    id
    response // The 'jsonb' custom data type
}
@designatednerd
Copy link
Contributor

The problem definitely seems to be in the fact that the value type in the dictionary is Optional<Any>, and that's what it's trying to use to create a key.

Does it work to typealias it to [String: JSONDecodable?] instead?

@nateirwin
Copy link
Author

I'll test that here shortly and report back.

@designatednerd
Copy link
Contributor

@nateirwin Were you ever able to get this tested out?

@nateirwin
Copy link
Author

@designatednerd: I started to dive into this, ran into a minor issue (that's project-specific and I just need to push through), then got pulled into another issue altogether. This is still critical for our project and it's next on my list, so I'll get to it soon.

I'm happy to close this and re-open when I've had the chance to test, if that's helpful.

@designatednerd
Copy link
Contributor

Nah, leave it open for now, I'll keep annoying you about it from time to time 😇

@nateirwin
Copy link
Author

Ok, reporting back :-)

Switching the typealias to public typealias jsonb = [String : JSONDecodable?] throws an error when loading a query that has a jsonb property on it:

Printing description of error:
▿ GraphQLResultError
  ▿ path : communities.0.organization_community_memberships.1.organization.paper_maps.0.paper_map.status
    ▿ head : Optional<Node>
      ▿ some : <Node: 0x11098f010>
  ▿ underlying : JSONDecodingError
    ▿ couldNotConvert : 2 elements
      ▿ value : 24 elements
        ▿ 0 : 2 elements
          - key : status
          - value : Uploading tiles complete
        ▿ 1 : 2 elements
          - key : min_zoom
          - value : 9
        ▿ 2 : 2 elements
          - key : extent_max_lon
          - value : -100.1
        ▿ 3 : 2 elements
          - key : extent_min_lat
          - value : 37.1
        ▿ 4 : 2 elements
          - key : tileset
          - value : tileset_548
        ▿ 5 : 2 elements
          - key : pdfPath
          - value : https://test.xyz/test.pdf
        ▿ 6 : 2 elements
          - key : updated_at
          - value : 2019-07-29T15:04:09.623Z
        ▿ 7 : 2 elements
          - key : extent_max_lat
          - value : 34.1
        ▿ 8 : 2 elements
          - key : localTilesPath
          - value : https://test.xyz/tiles/
        ▿ 9 : 2 elements
          - key : geotiff_path
          - value : /image_georef.tif
        ▿ 10 : 2 elements
          - key : geotiff_3857_path
          - value : /image_georef_3857.tif
        ▿ 11 : 2 elements
          - key : name
          - value : Name Here
        ▿ 12 : 2 elements
          - key : max_zoom
          - value : 15
        ▿ 13 : 2 elements
          - key : id
          - value : 548
        ▿ 14 : 2 elements
          - key : temp_georefTilesPath
          - value : <null>
            - super : NSObject
        ▿ 15 : 2 elements
          - key : ground_control_points
          - value : [[1071.640625,2925.1640625,-122.104733996093,37.714753539402],[1355.44921875,2053.29296875,-122.09752430208,37.7391649876836],[1192.0390625,1299.8125,-122.105021746829,37.7592430319794],[669.765625,594.1796875,-122.124813348055,37.7770496273366]]
        ▿ 16 : 2 elements
          - key : png_path
          - value : /final.png
        ▿ 17 : 2 elements
          - key : page_number
          - value : <null> { ... }
        ▿ 18 : 2 elements
          - key : image_width
          - value : 2550
        ▿ 19 : 2 elements
          - key : created_at
          - value : 2019-07-29T14:57:07.796Z
        ▿ 20 : 2 elements
          - key : georefTilesPath
          - value : https://test.xyz/tiles_georef
        ▿ 21 : 2 elements
          - key : image_height
          - value : 3300
        ▿ 22 : 2 elements
          - key : extent_min_lon
          - value : -115.1
        ▿ 23 : 2 elements
          - key : file_type
          - value : pdf
      - to : Swift.Dictionary<Swift.String, Swift.Optional<Apollo.JSONDecodable>>

I'm guessing maybe it's bonking on the null values? Honestly, we've run into so many issues with this custom jsonb scalar type that I'm considering trying to back out of using it altogether.

@designatednerd
Copy link
Contributor

Hm, shouldn't be bonking on nulls, we've got something that theoretically should be handling that. From the underlying error it looks like it's freaking out about the status key which is just a String.

I think a bigger question this brings up is why this data needs to be returned as an arbitrary JSON blob in the first place - why isn't this data returned as something typed?

@nateirwin
Copy link
Author

Well, status is the name of the jsonb property, which (I think) should be of type [String: JSONDecodable?] because of the type alias:

public typealias jsonb = [String : JSONDecodable?]

extension Dictionary: JSONDecodable {
    public init(jsonValue value: JSONValue) throws {
        guard let dictionary = value as? Dictionary else {
            throw JSONDecodingError.couldNotConvert(value: value, to: Dictionary.self)
        }
        
        self = dictionary
    }
}

That's why I'm scratching my head about this. Maybe my next step is setting some more breakpoints in JSONStandardTypeConversions.swift?

@designatednerd
Copy link
Contributor

Looking at the JSON, status seems to be a key and value of the dictionary which isn't getting deserialized - can you throw in a breakpoint and print out the raw JSON string (or use a proxying tool to see it) that's coming through? That might help.

@nateirwin
Copy link
Author

Sure, here you go (sorry about the delay):

{
  "data": {
    "communities": [
      {
        "organization_community_memberships": [
          {
            "id": 467,
            "organization": {
              "paper_maps": [
                {
                  "id": 557,
                  "paper_map": {
                    "description": "null",
                    "id": 322,
                    "name": "Name Here",
                    "status": {
                      "id": 548,
                      "name": "Name Here",
                      "status": "Uploading tiles complete",
                      "pdfPath": "https://test.xyz/test.pdf",
                      "max_zoom": 15,
                      "min_zoom": 9,
                      "png_path": "/final.png",
                      "file_type": "pdf",
                      "created_at": "2019-07-29T14:57:07.796Z",
                      "updated_at": "2019-07-29T15:04:09.623Z",
                      "image_width": 2550,
                      "page_number": null,
                      "geotiff_path": "/image_georef.tif",
                      "image_height": 3300,
                      "extent_max_lat": "34.1",
                      "extent_max_lon": "-100.1",
                      "extent_min_lat": "37.1",
                      "extent_min_lon": "-115.1",
                      "localTilesPath": "https://test.xyz/tiles/",
                      "mapbox_tileset": "trailheadlabs.paper_map_548",
                      "georefTilesPath": "https://test.xyz/tiles_georef",
                      "geotiff_3857_path": "/image_georef_3857.tif",
                      "temp_georefTilesPath": null,
                      "ground_control_points": "[[1071.640625,2925.1640625,-122.104733996093,37.714753539402],[1355.44921875,2053.29296875,-122.09752430208,37.7391649876836],[1192.0390625,1299.8125,-122.105021746829,37.7592430319794],[669.765625,594.1796875,-122.124813348055,37.7770496273366]]"
                    }
                  }
                }
              ]
            }
          }
        ]
      }
    ]
  }
}

@designatednerd
Copy link
Contributor

designatednerd commented Jul 16, 2020

Ah, ok, status is also the name of the property of the dictionary, not just one of the things in the dictionary.

One thing it didn't occur to me to ask: Is your Dictionary initializer getting hit at all? I wonder if it may need to be constrained to the key and value types you're using

@nateirwin
Copy link
Author

It is getting hit, and it's throwing when that same status property that holds the dictionary.

@designatednerd
Copy link
Contributor

designatednerd commented Jul 17, 2020

Here's a thought - have you got an extension that implements JSONEncodable? It looks like you're only implementing JSONDecodable above

@nateirwin
Copy link
Author

Hmm, this is what I get when I start to implement JSONEncodable:

Screen Shot 2020-07-16 at 8 35 13 PM

@designatednerd
Copy link
Contributor

Aha, that is indeed implemented in the library.

OK, I'll mess around with this some to try and figure out what's going on here.

@nateirwin
Copy link
Author

Thanks for your help!

@designatednerd
Copy link
Contributor

@nateirwin please try pulling the branch #1317 is opened from and seeing if that fixes your issue. You can put the typealias back to [String: Any?].

@nateirwin
Copy link
Author

Yep, that fixes the issue!!!

@designatednerd designatednerd added this to the Next Release milestone Jul 17, 2020
@designatednerd
Copy link
Contributor

This has shipped with 0.30.0 - ready to go for SPM and Carthage, in the process of pushing to trunk on Cocoapods. 🎉

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

No branches or pull requests

2 participants