Home > Software design >  Leading zeros cut from JSON response when using enum
Leading zeros cut from JSON response when using enum

Time:05-07

Essentially I am using enum to account for instances where code is either and int or string - but it appears when the JSON value is simply an int with leading 0's like 0039223 they get trimmed out. How can this be prevented?

Try running this code in Playgrounds:

let data = """
[
  {
    "date": "2022-05-04",
    "code": 0039223,
    "notes": "Take keys"
  },
  {
    "date": "2022-05-04",
    "code": "Gate: 2312231",
    "notes": "Take Box"
  }
]
""".data(using: .utf8)!

enum Code: Decodable {
    case int(Int)
    case string(String)
}

struct Item: Decodable {
    var date: Date
    var code: Code
    var notes: String

    enum CodingKeys: String, CodingKey {
        case date, code, notes
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.date = try container.decode(Date.self, forKey: .date)
        self.notes = try container.decode(String.self, forKey: .notes)

        if let value = try? container.decode(Int.self, forKey: .code) {
            self.code = .int(value)
        } else if let value = try? container.decode(String.self, forKey: .code) {
            self.code = .string(value)
        } else {
            let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unable to decode value for `code`")
            throw DecodingError.typeMismatch(Code.self, context)
        }
    }
}

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)

let items = try! decoder.decode([Item].self, from: data)

for item in items {
    print("date: \(item.date.description)")
    print("code: \(item.code)")
    print("notes: \(item.notes)")
    print()
}

CodePudding user response:

The JSON is invalid. You could, theoretically, clean it up, removing leading zeros in numeric occurrences of "code". E.g., convert to a string, trim leading zeros (e.g., with regular expression), and then convert back to a Data:

guard
    let string = String(data: data, encoding: .utf8)?
        .replacingOccurrences(of: #""code"\s*:\s*0 (?!\b)"#, with: #""code": "#, options: .regularExpression),
    let fixedData = string.data(using: .utf8)
else { return }

That converts the invalid JSON:

[
  {
    "date": "2022-05-04",
    "code": 0039223,
    "notes": "Take keys"
  },
  {
    "date": "2022-05-04",
    "code": "Gate: 2312231",
    "notes": "Take Box"
  }
]

Into:

[
  {
    "date": "2022-05-04",
    "code": 39223,
    "notes": "Take keys"
  },
  {
    "date": "2022-05-04",
    "code": "Gate: 2312231",
    "notes": "Take Box"
  }
]

And then you can decode fixedData.


Alternatively, you could make code a String, eliminating all of that custom decoder code:

struct Item: Decodable {
    var date: Date
    var code: String
    var notes: String
}

And then have the regex quote the unquoted integers, making them parsable as strings:

guard
    let string = String(data: data, encoding: .utf8)?
        .replacingOccurrences(of: #""code"\s*:\s*(\d )"#, with: #""code": "$1""#, options: .regularExpression),
    let data2 = string.data(using: .utf8)
else { return }

That converts:

[
  {
    "date": "2022-05-04",
    "code": 0039223,
    "notes": "Take keys"
  },
  {
    "date": "2022-05-04",
    "code": "Gate: 2312231",
    "notes": "Take Box"
  }
]

Into

[
  {
    "date": "2022-05-04",
    "code": "0039223",
    "notes": "Take keys"
  },
  {
    "date": "2022-05-04",
    "code": "Gate: 2312231",
    "notes": "Take Box"
  }
]

That streamlines the server response into something that doesn’t require the custom decoder.


But both of these are inefficient, brittle, and a little nuts. The JSON is incorrect and it should be fixed at the source, not client-side.

CodePudding user response:

So, based on Check if a json value match a number with leading zero and convert it to a string and rfc4267, JSON does not support integers with leading zeros.

So, how do you fix it? The proper answer is to fix it at the source, what ever is generating the JSON should be updated to meet the standard requirements of the format, but, having spent my fair time with back end developers, that might be "to hard" (if you think it's "to hard" for the producers, why would you think it's going to be any easier for the consumers?

  • Related