1

Problem

I'm currently getting JSON from a server that I don't have access to. The JSON I sometimes get will put this character \u0000 at the end of a String. As a result my decoding fails because this character just fails it.

I'm trying to debug this in Playground but I keep getting this error.

Expected hexadecimal code in braces after unicode escape

Here is some sample code to try out.

import UIKit
import Foundation

struct GroceryProduct: Codable {
    var name: String
}

let json = """
{
    "name": "Durian \u0000"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let product = try decoder.decode(GroceryProduct.self, from: json)

print(product.name)

Question

How do you deal with \u0000 from JSON? I have been looking at DataDecodingStrategy from the Apple documentation but I can't even test anything out because the Playground fails to even run.

Any direction or advice would be appreciated.


Update

Here is some more setup code to tryout in your Playground or a real app.

JSON test.json

{
    "name": "Durian \u0000"
}

Code

extension Bundle {
    func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
        
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }
                
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy    = .deferredToDate
        decoder.keyDecodingStrategy     = .useDefaultKeys

        do {
            return try decoder.decode(T.self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
        } catch {
            fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
        }
    }
}


struct GroceryProduct: Codable {
    var name: String
}

// Try running this and it won't work
let results = Bundle.main.decode(GroceryProduct.self, from: "test.json")


print(results.name)
alenm
  • 1,013
  • 3
  • 15
  • 34
  • 2
    I tried your Playground code with Xcode 11.6 (your JSON file should be `test.json`, not `Text.json`) and could not reproduce the error `Expected hexadecimal code in braces after unicode escape`. I think it is just a problem of String literal, not a problem of decoding JSON. Generally, `\u0000` is valid in JSON string and represents U+0000 NUL. If that is extraneous and should not be there, it is a bug of the API and you should better send a bug report to the server side engineer. – OOPer Aug 07 '20 at 23:20
  • I wish I could create a bug report for the server side engineer, but in my situation I can't – alenm Aug 07 '20 at 23:46

1 Answers1

2

You need to escape \u0000 characters first - this can be done before decoding:

guard let data = try? Data(contentsOf: url) else {
    fatalError("Failed to load \(file) from bundle.")
}

let escaped = Data(String(data: data, encoding: .utf8)!.replacingOccurrences(of: "\0", with: "").utf8)
...
return try decoder.decode(T.self, from: escaped)

Note: force-unwrapping for simplicity only.


In Playground you can escape it with an additional \ (to make it work):

let json = """
{
    "name": "Durian \\u0000"
}
""".data(using: .utf8)!

or replace it with \0 (to make it fail - behave like during the decoding):

let json = """
{
    "name": "Durian \0"
}
""".data(using: .utf8)!
pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • The problem with your solution is the JSON I'm getting is coming from another server. So I was using a playground to try to debug my problem. In real life I don't have access to the JSON server and I can't ask them to add a backslash. – alenm Aug 07 '20 at 22:38
  • I adde some more details in my original question for testing with a local json file. I don't see where I would put `String(data: json, encoding: .utf8)!.replacingOccurrences(of: "\0", with: "").data(using: .utf8)!` – alenm Aug 07 '20 at 23:06
  • thanks. This lead me down the right path. I appreciate it. – alenm Aug 07 '20 at 23:47
  • 2
    No need to force unwrap when converting utf8 to data (even though it is safe, it will never fail). `Data("string".utf8)` – Leo Dabus Aug 08 '20 at 00:54