Home > other >  Swift Convert UnionValue JSON to Object and loop
Swift Convert UnionValue JSON to Object and loop

Time:10-07

Received Json response

{
"data": [
    {
        "key": "email",
        "value": "[email protected]"
    },
    {
        "key": "name",
        "value": "Tg baa"
    },
    {
        "key": "dob",
        "value": "1999-06-06"
    },
    {
        "key": "nationality",
        "value": "UK"
    },
    {
        "key": "address",
        "value": [
            {
                "key": "addr1",
                "value": "Bundle road, near church 460102"
            },
            {
                "key": "postalCode",
                "value": "46HG02"
            },
            {
                "key": "city",
                "value": "London"
            }
        ]
    }
],
"Greeting": "Birthday",
"Service": "mydelivery"

}

Generated model using online tool

    // MARK: - Welcome
public struct Welcome: Codable {
    public let data: [Datum]
    public let Greeting, Service: String
}

// MARK: - Datum
public struct Datum: Codable {
    public let key: String
    public let value: ValueUnion
}

public enum ValueUnion: Codable {
    case string(String)
    case valueElementArray([ValueElement])

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode([ValueElement].self) {
            self = .valueElementArray(x)
            return
        }
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        throw DecodingError.typeMismatch(ValueUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ValueUnion"))
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let x):
            try container.encode(x)
        case .valueElementArray(let x):
            try container.encode(x)
        }
    }
}

// MARK: - ValueElement
public struct ValueElement: Codable {
    let key, value: String
}

Try to access the address array in the model. but always fails

print("onboardingStatus: \(result?.Greeting ?? "")")
        print("idService: \(result?.Service ?? "")")
        guard let userData = result?.data else { return }
        for item in userData {
            if item.key == "address" {
                guard let address_info = item.value as? [ValueElement] else { return }
                
                for ad in address_info {
                    print(ad.key)
                    print(ad.value)
                }
            }
            print(item.key)
            print(item.value)
            
        }

I want to loop through the key and values of address. but am getting error "For-in loop requires 'ValueUnion' to conform to 'Sequence'" . please help.

CodePudding user response:

The compiler error you should get on:

guard let address_info = item.value as? [ValueElement] else { return }

is

Cast from 'ValueUnion' to unrelated type '[ValueElement]'

Not "For-in loop requires 'ValueUnion' to conform to 'Sequence'".

The error is explicit, address_info is a ValueUnion, not a [ValueElement]. In your specific case, it's almost that, but since it's a ValueUnion, it could be a String instead. Afterall, ValueUnion is a associated value enum to handle a String and a Array of ValueElement.

The quick "fix" would be:

if case let .valueElementArray(address_info) = item.value {
    for ad in address_info {
        print(ad.key)
        print(ad.value)
    }
}

But it might be too advanced (and isn't useful if you don't understand it).

switch item.value {
    case .string(let aString):
        print("Found a string: \(aString)")
    case .valueElementArray(let elements):
        for ad in elements {
            print(ad.key)
            print(ad.value)
        }
}

Now, your JSON is strange, and you might want in the end a model, so either have a "mapping" beetween you current model into that one, or with some work (maybe too much work for the result), decode directly into this model.

struct UserOrSomething {
    let greeting: String
    let service: String
    let infos: Infos
}
struct Infos {
    let name: String
    let dob: String
    let email: String
    let nationality: String
    let address: Address
}

struct Address {
    let addr1: String
    let addr2: String
    let postalCode: String
    let city: String
    let state: String
    let region: String
}

CodePudding user response:

Your ValueUnion could be one of two distinct values, either a single String, or an array of key/value pairs. You say you "want to cast to [ValueElement]" so you can loop through them, but what if the ValueUnion doesn't contain an array of value elements?

First you have to find out if an array even exists in the value union...

func example(valueUnion: ValueUnion) {
    if case let .valueElementArray(array) = valueUnion {
        for each in array {
            print("valueElement - key:", each.key, " value:", each.value)
        }
    }
}
  • Related