Skip to content

JSONEncoder can't encode what JSONDecoder decoded (they disagree on limits...) #1176

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

Open
weissi opened this issue Feb 15, 2025 · 3 comments
Assignees

Comments

@weissi
Copy link
Contributor

weissi commented Feb 15, 2025

Consider JSONs like

{
  "nestedArray": [
    {
      "nestedArray": [
        {
          "nestedArray": [
            {
              "nestedArray": [
                {
                  "nestedArray": [
                    {
                      "nestedArray": []
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

but deeper (for example bug1.json which has 255 levels). Now consider this program which decodes a thing and then reencodes it:

import Foundation

struct Thing: Codable {
    var nestedArray: [Thing]?
}

do {
    let data = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments.dropFirst().first ?? "/tmp/file.json"))
    let decoded = try JSONDecoder().decode(Thing.self, from: data)
    do {
        let encoded = try JSONEncoder().encode(decoded)
        print("OK", encoded)
    } catch {
        print("encode ERROR", error)
    }
} catch {
    print("decode (or earlier) ERROR:", error)
}

You'd expect that JSONEncoder can encode everything that JSONDecoder decoded. But alas, here's what we see:

$ swiftc -O test.swift && ./test bug1.json
encode ERROR invalidValue(test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([test.Thing(nestedArray: Optional([]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))]))])), Swift.EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Too many nested arrays or dictionaries." UserInfo={NSDebugDescription=Too many nested arrays or dictionaries.})))

bug1.json

@YOCKOW
Copy link
Member

YOCKOW commented Mar 28, 2025

👀 The max number of depth is hardcoded as 512 for both decoding and encoding.

💭 Not yet tested, but my guess is that the value of depth for decoding starts with 0 but the one for encoding starts with 1, because JSONScanner increments depth after checking it, on the other hand, JSONWriter increments depth before checking it.

mutating func serializeJSON(_ value: JSONEncoderValue, depth: Int = 0) throws {
switch value {
case .string(let str):
serializeString(str)
case .bool(let boolValue):
writer(boolValue ? "true" : "false")
case .number(let numberStr):
writer(contentsOf: numberStr.utf8)
case .array(let array):
try serializeArray(array, depth: depth + 1)
case .nonPrettyDirectArray(let arrayRepresentation):
writer(contentsOf: arrayRepresentation)
case let .directArray(bytes, lengths):
try serializePreformattedByteArray(bytes, lengths, depth: depth + 1)
case .object(let object):
try serializeObject(object, depth: depth + 1)
case .null:
writer("null")
}
}

@jevonmao
Copy link

jevonmao commented Apr 6, 2025

@YOCKOW Does this need a fix? If so, can I look into opening a PR to address this?

@YOCKOW
Copy link
Member

YOCKOW commented Apr 7, 2025

@YOCKOW Does this need a fix? If so, can I look into opening a PR to address this?

Sure, you can. I'm assigning you to this issue, thank you.

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

No branches or pull requests

3 participants