apollographql / apollo-ios

📱  A strongly-typed, caching GraphQL client for iOS, written in Swift.
https://www.apollographql.com/docs/ios/
MIT License
3.89k stars 728 forks source link

Custom Scalar | ApolloAPI.JSONDecodingError.couldNotConvert(value: AnyHashable to: Swift.String #3367

Closed PareshPatel721 closed 7 months ago

PareshPatel721 commented 7 months ago

Question

I'm using Apollo 1.9.3 with local package support. So there are no any run script is needed to set in build phase. When i invoke My API from graphql server I'm getting error as ApolloAPI.JSONDecodingError.couldNotConvert(value: AnyHashable to: Swift.String

Local framework default generate JSON file in CustomScalar folder and file only contains public typealias JSON = String

calvincestari commented 7 months ago

Hi @PareshPatel721 - since you mention the build phase script it sounds like you're familiar with the old 0.x versions. 1.0 has changed code generation quite a bit. We have a 1.0 migration guide and there is a specific section about custom scalars.

tl;dr - you need to write the custom scalar code yourself, just like before but in a different place now.

github-actions[bot] commented 7 months ago

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo iOS usage and allow us to serve you better.

PareshPatel721 commented 7 months ago

I've written custom scalar code, but it still not working public typealias JSON = String (this is what i get default implementation)

In same file I've try to extend many ways like below

public typealias JSON = String

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

self = dictionary

} }`

`extension SchemaConfiguration { public enum JSON: CustomScalarType, Hashable { case dictionary([String: AnyHashable]) case array([AnyHashable])

    public init(_jsonValue value: JSONValue) throws {
        if let dict = value as? [String: AnyHashable] {
            self = .dictionary(dict)
        } else if let array = value as? [AnyHashable] {
            self = .array(array)
        } else {
            throw JSONDecodingError.couldNotConvert(value: value, to: JSON.self)
        }
    }

    public var _jsonValue: JSONValue {
        switch self {
        case let .dictionary(json as AnyHashable),
             let .array(json as AnyHashable):
            return json
        }
    }

    public static func == (lhs: JSON, rhs: JSON) -> Bool {
        lhs._jsonValue == rhs._jsonValue
    }

    public func hash(into hasher: inout Hasher) {
        hasher.combine(_jsonValue)
    }
}

}`

`extension Schema { public enum JSON: CustomScalarType, Hashable { case dictionary([String: AnyHashable]) case array([AnyHashable])

    public init(_jsonValue value: JSONValue) throws {
        if let dict = value as? [String: AnyHashable] {
            self = .dictionary(dict)
        } else if let array = value as? [AnyHashable] {
            self = .array(array)
        } else {
            throw JSONDecodingError.couldNotConvert(value: value, to: JSON.self)
        }
    }

    public var _jsonValue: JSONValue {
        switch self {
        case let .dictionary(json as AnyHashable),
             let .array(json as AnyHashable):
            return json
        }
    }

    public static func == (lhs: JSON, rhs: JSON) -> Bool {
        lhs._jsonValue == rhs._jsonValue
    }

    public func hash(into hasher: inout Hasher) {
        hasher.combine(_jsonValue)
    }
}

}`

`extension JSON: JSONDecodable, JSONEncodable { public init(jsonValue value: JSONValue) throws {

    let string = value as? String

    if (string == nil) {
        do {
            let data1 =  try JSONSerialization.data(withJSONObject: value, options: JSONSerialization.WritingOptions.prettyPrinted) // first of all convert json to the data
            let convertedString = String(data: data1, encoding: String.Encoding.utf8) // the data will be converted to the string
            if (convertedString == nil) {
                throw JSONDecodingError.couldNotConvert(value: value, to: String.self)
            } else {
                self = convertedString ?? ""
            }
        } catch let myJSONError {
            print(myJSONError)
            throw JSONDecodingError.couldNotConvert(value: value, to: String.self)
        }
    } else {
        self = string ?? ""
    }
}
public var jsonValue: JSONValue {
    return self
}

}`

` extension SchemaConfiguration { public enum JSON: CustomScalarType, Hashable { case dictionary([String: AnyHashable]) case array([AnyHashable])

    public init(_jsonValue value: JSONValue) throws {
        if let dict = value as? [String: AnyHashable] {
            self = .dictionary(dict)
        } else if let array = value as? [AnyHashable] {
            self = .array(array)
        } else {
            throw JSONDecodingError.couldNotConvert(value: value, to: JSON.self)
        }
    }

    public var _jsonValue: JSONValue {
        switch self {
        case let .dictionary(json as AnyHashable),
             let .array(json as AnyHashable):
            return json
        }
    }

    public static func == (lhs: JSON, rhs: JSON) -> Bool {
        lhs._jsonValue == rhs._jsonValue
    }

    public func hash(into hasher: inout Hasher) {
        hasher.combine(_jsonValue)
    }
}

} `

Please help me out where and how i can write custom scalar code, so my response get automatically parsed and confirmed to custom scalar

Thank you

calvincestari commented 7 months ago

Codegen is generating public typealias JSON = String because that is the default for custom scalars in 1.0. We use String because that is most likely the data type used to transport the data in the JSON payload and simple execution of the default generated operation model would succeed.

You are able to edit this file and the codegen engine will not overwrite it during the next code generation. So, if your custom scalar is not a String then you must edit the code in that file and write the new code for your custom scalar. All this information can be found in our documentation on Custom Scalars.

Firstly you need to delete public typealias JSON = String since your custom scalar is not String.

Of the code you posted above this one looks correct but you might not need to put it inside an extension depending on how you created your schema module:

public enum JSON: CustomScalarType, Hashable {
  case dictionary([String: AnyHashable])
  case array([AnyHashable])

  public init(_jsonValue value: JSONValue) throws {
    if let dict = value as? [String: AnyHashable] {
      self = .dictionary(dict)
    } else if let array = value as? [AnyHashable] {
      self = .array(array)
    } else {
      throw JSONDecodingError.couldNotConvert(value: value, to: JSON.self)
    }
  }

  public var _jsonValue: JSONValue {
    switch self {
    case
      let .dictionary(json as AnyHashable),
      let .array(json as AnyHashable):
      return json
    }
  }

  // You don't need to manually define `Equatable` and `Hashable` conformance the compiler will synthesize that for you.
}
PareshPatel721 commented 7 months ago

@calvincestari That is working It saves my million of time Thank you for your time and effort