jsonswiftencodejsonencoder

Encode is giving error as "The data couldn't be written because it isn't in the correct format"


I have one API where based on parameters data is changing.

Response 1


{
  "success": true,
  "statusCode": 200,
  "errorLst": [],
  "succcessMessage": null,
  "resultData": [
    {
      "plotSize": 1,
      "equationValue": null
    },
    {
      "plotSize": 2,
      "equationValue": "*1.5"
    },
    {
      "plotSize": 3,
      "equationValue": "*2.5"
    }
  ]
}

Response 2

{
  "success": true,
  "statusCode": 200,
  "errorLst": [],
  "succcessMessage": null,
  "resultData": [
    {
      "plotSize": 4,
      "equationValue": "*4"
    },
    {
      "plotSize": 5,
      "equationValue": "*5.5"
    },
    {
      "plotSize": 6,
      "equationValue": "*6.5"
    }
  ]
}

I parse the API data and try to convert the base model data to my actual mode data:

let myEndPoint = EndPoint(modelType: PlotModel.self)
self.plotModelModel = convertToModel(baseModelResponse: inner_apiResponse, endpoint: myEndPoint) as? PlotModel ?? PlotModel()

inner_apiResponse is nothing but the data I get from API parsed as BaseModel

Model I have is as below:

struct PlotModel : Codable {
    var success : Bool?
    var statusCode : Int?
    var succcessMessage : String?
    
    var resultData : [InnerPlotData]?
    
}

struct InnerPlotData : Codable {
    var plotSize : Int?
    var equationValue : String?
}

I am using below function to convert the base model to my model:

func convertToModel<T : Codable>(baseModelResponse : BaseModel, endpoint : EndPoint<T>) -> Any {
    
    do {
        let encoder = JSONEncoder()
        do {
            let jsonData = try encoder.encode(baseModelResponse)
            if let jsonString = String(data: jsonData, encoding: .utf8) {
                if let data = jsonString.data(using: .utf8) {
                    do {
                        let localModel = try JSONDecoder().decode(endpoint.modelType.self, from: data)
                        return localModel
                    } catch {
                        print("Error parsing JSON:", error)
                        return makeErrorBaseModel()
                    }
                } else {
                    print("Invalid JSON string")
                    return makeErrorBaseModel()
                }
            } else {
                return makeErrorBaseModel()
            }
        } catch let decodingError as DecodingError {
            switch decodingError {
                case .typeMismatch(_, let c), .valueNotFound(_, let c), .keyNotFound(_, let c), .dataCorrupted(let c):
                    "error.debugDescription=c===\(c.debugDescription)".printLog()
                    return makeErrorBaseModel()
            }
        } catch {
            "error.debugDescription==\(error.localizedDescription)".printLog()
            return makeErrorBaseModel()
        }
    } catch {
        return makeErrorBaseModel()
    }
    
}

Model I have is as below:

struct BaseModel : Codable {
    var success : Bool?
    var statusCode : Int?
    var succcessMessage : String?
    
    var resultData : AnyCodable?
    
}

@objcMembers final class AnyCodable: NSObject, Codable {
    
    let value: Any?
    
    init(_ value: Any?) {
        self.value = value
    }
    
    required init(from decoder: Decoder) throws {
        
        let container = try decoder.singleValueContainer()
        
        if let value = try? container.decode(String.self) {
            self.value = value
        } else if let value = try? container.decode(Bool.self) {
            self.value = value
        } else if let value = try? container.decode(Int.self) {
            self.value = value
        } else if let value = try? container.decode(Double.self) {
            self.value = value
        } else if let value = try? container.decode(Float.self) {
            self.value = value
        } else if container.decodeNil() {
            self.value = nil
        } else if let value = try? container.decode([String: AnyCodable].self) {
            self.value = value.mapValues { $0.value }
        } else if let value = try? container.decode([AnyCodable].self) {
            self.value = value.map { $0.value }
        }  else {
            throw DecodingError.dataCorruptedError(
                in: container,
                debugDescription: "Invalid value cannot be decoded"
            )
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch value {
            case let bool as Bool:
                try container.encode(bool)
            case let int as Int:
                try container.encode(int)
            case let float as Float:
                try container.encode(float)
            case let double as Double:
                try container.encode(double)
            case let string as String:
                try container.encode(string)
            case let array as [Any?]:
                try container.encode(array.map { AnyCodable($0) })
            case let dictionary as [String: Any?]:
                try container.encode(dictionary.mapValues { AnyCodable($0) })
            case let encodable as Encodable:
                try encodable.encode(to: encoder)
            default:
                let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyEncodable value cannot be encoded")
                throw EncodingError.invalidValue(value ?? "InvalidValue", context)
        }
    }
}

Response 2 is working fine, however Response 1 is giving error as below:

error.debugDescription==The data couldn’t be written because it isn’t in the correct format.

Exception is thrown at let jsonData = try encoder.encode(baseModelResponse) line.

Why is this happening and how can it be fixed?

Minimal Code to check

https://wetransfer.com/downloads/08ebea0e519f58023444c36727e4afdc20240820150159/adf188


After further check as per vadian, I found that in func encode, I get exception as below.

invalidValue("InvalidValue", Swift.EncodingError.Context(codingPath: [CodingKeys(stringValue: "resultData", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), _JSONKey(stringValue: "equationValue", intValue: nil)], debugDescription: "AnyEncodable value cannot be encoded", underlyingError: nil))

However the equationValue is of type String as mentioned in the model


Solution

  • The value that's failing is:

      "equationValue": null
    

    You forgot to include a case for that:

    case nil:
        try container.encodeNil()
    

    A better approach for you may be a JSON type that can hold "something that can be expressed in JSON" without resorting to Any.