How to create a struct to match this Json
You want to learn how to do this on your own but there are tools on the web that can easily help you create a struct
from a JSON. This is a really good one.
You were close so make sure you dissect the code and notice the differences
import SwiftUI
class ResultsViewModel: ObservableObject{
let json = """
{
"results": [
{
"gender": "male",
"name": {
"title": "mr",
"first": "brad",
"last": "gibson"
},
"location": {
"street": "9278 new road",
"city": "kilcoole",
"state": "waterford",
"postcode": "93027",
"coordinates": {
"latitude": "20.9267",
"longitude": "-7.9310"
},
"timezone": {
"offset": "-3:30",
"description": "Newfoundland"
}
},
"email": "brad.gibson@example.com",
"login": {
"uuid": "155e77ee-ba6d-486f-95ce-0e0c0fb4b919",
"username": "silverswan131",
"password": "firewall",
"salt": "TQA1Gz7x",
"md5": "dc523cb313b63dfe5be2140b0c05b3bc",
"sha1": "7a4aa07d1bedcc6bcf4b7f8856643492c191540d",
"sha256": "74364e96174afa7d17ee52dd2c9c7a4651fe1254f471a78bda0190135dcd3480"
},
"dob": {
"date": "1993-07-20T09:44:18.674Z",
"age": 26
},
"registered": {
"date": "2002-05-21T10:59:49.966Z",
"age": 17
},
"phone": "011-962-7516",
"cell": "081-454-0666",
"id": {
"name": "PPS",
"value": "0390511T"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/75.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/75.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg"
},
"nat": "IE"
}
],
"info": {
"seed": "fea8be3e64777240",
"results": 1,
"page": 1,
"version": "1.3"
}
}
""".data(using: .utf8)
var result: JSONObject? {
var decoded: JSONObject? = nil
do{
decoded = try JSONDecoder().decode(JSONObject.self, from: json!)
}catch{
print(error)
}
return decoded
}
}
struct ResultsView: View {
@StateObject var vm: ResultsViewModel = ResultsViewModel()
var body: some View {
if vm.result == nil || vm.result?.results.first == nil{
Text("nil")
}else{
VStack{
Text(vm.result!.results.first!.name.first)
Text(vm.result!.results.first!.name.last)
}
}
}
}
// MARK: - JSONObject
struct JSONObject: Codable {
let results: [Result]
let info: Info
}
// MARK: - Info
struct Info: Codable {
let seed: String
let results, page: Int
let version: String
}
// MARK: - Result
struct Result: Codable {
let gender: String
let name: Name
let location: Location
let email: String
let login: Login
let dob, registered: Dob
let phone, cell: String
let id: ID
let picture: Picture
let nat: String
}
// MARK: - Dob
struct Dob: Codable {
let date: String
let age: Int
}
// MARK: - ID
struct ID: Codable {
let name, value: String
}
// MARK: - Location
struct Location: Codable {
let street, city, state, postcode: String
let coordinates: Coordinates
let timezone: Timezone
}
// MARK: - Coordinates
struct Coordinates: Codable {
let latitude, longitude: String
}
// MARK: - Timezone
struct Timezone: Codable {
let offset, timezoneDescription: String
enum CodingKeys: String, CodingKey {
case offset
case timezoneDescription = "description"
}
}
// MARK: - Login
struct Login: Codable {
let uuid, username, password, salt: String
let md5, sha1, sha256: String
}
// MARK: - Name
struct Name: Codable {
let title, first, last: String
}
// MARK: - Picture
struct Picture: Codable {
let large, medium, thumbnail: String
}
How do I create a decodable struct to match JSON nest from API?
There are a couple of issues. Firstly, your JSON data is an array of objects and your struct is just an object. So, first create a struct to model each object. Also, using var in structs is not very useful considering structs have to be destroyed and recreated with a changed property to be modified (energy intensive process). It should look like this:
struct SingleData: Codable {
let open: Double
let high: Double
}
Now, ScrapeStruct should have a property called data that should be an array of SingleData. It should look like this:
struct ScrapeStruct: Decodable {
let data: [SingleData]
}
Now, as for your second struct. It will not work because "high" and "low" are types that do not exist. To parse (essentially converting languages, in this case JSON to Swift) you can only use basic types or objects created with basic types (such as Date, Int, String, Double, etc)
struct ScrapeStruct: Decodable {
var data: high //type high does not exist
var data: low //type low does not exist
}
How to structure my models to match my JSON?
Your APIResponse
should support generic.
public class APIResponse<T>
{
public int Status { get; set; }
public string Message { get; set; }
public T Data { get; set; }
}
As the data
is a LeaveModel
object but not a LeaveModel
array.
And deserialize as APIResponse<LeaveModel>
type.
APIResponse<LeaveModel> apiResponse = JsonConvert.DeserializeObject<APIResponse<LeaveModel>>(response.Content.ReadAsStringAsync().Result);
LeaveModel leave = apiResponse.Data;
And would suggest changing the method to asynchronous.
public async Task<IActionResult> APICallAsync()
{
//Fetch the JSON string from URL.
LeaveModel leave = new LeaveModel();
string apiUrl = "http://xxxxx.xxx.xxx/GetLeaveBalance/2170";
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(apiUrl);
if (response.IsSuccessStatusCode)
{
APIResponse<LeaveModel> apiResponse = JsonConvert.DeserializeObject<APIResponse<LeaveModel>>(await response.Content.ReadAsStringAsync());
leave = apiResponse.Data;
}
//Return the Deserialized JSON object.
return Json(leave);
}
Creating Structs That Match JSON Content in Swift
friends
is not a String array, it should also be a Struct:
struct Result: Codable {
let data: [PeopleData]
}
struct FriendData: Codable {
let name: String
let address: String
}
struct PeopleData: Codable {
let name: String
let address: String
let friends: [FriendData]
}
should work.
Nitpick: If you do not need to encode those back, Decodable
should be enough instead of Codable
.
P.S: QuickType is also a great tool for this.
How to make struct with this json?
I think the structs you have are very close to what is required, just minor tweaks such as:
struct Response: Codable{
var err_code: Int
var data: DataObj
}
struct DataObj: Codable{
var message: String
var result: Result
}
struct Result: Codable {
var check_in: [Checkin]
}
struct Checkin: Codable, Hashable {
var created_at: String
var late: String
var location: String
var place: String
var folder: String
var tujuan: String
var link: String
}
Use it like this:
struct ContentView: View {
@State var apiResponse: Response?
var body: some View {
VStack {
if let response = apiResponse {
Text("message: \(response.data.message) ")
Text("check_in.count: \(response.data.result.check_in.count) ")
List {
ForEach(response.data.result.check_in, id: \.self) { chk in
Text("created_at: \(chk.created_at) ")
}
}
}
}
.onAppear {
let jsonStr = """
{
"err_code": 0,
"data": {
"message": "Success",
"result": {
"check_in": [
{
"created_at": "2022-06-20 10:00:37",
"late": "True",
"location": "Jl. H. Asmawi No.63, Beji, Kecamatan Beji, Kota Depok, Jawa Barat 16421, Indonesia",
"place": "GRIT OFFICE",
"folder": "./pegawai/grit/2022/06/20/masuk/10/00/",
"filename": "0000_rakha_fatih_athallah_picture.jpg",
"tujuan": "",
"link": "http://103.140.90.10:8081/face-recognition/client/python/pegawai/grit/2022/06/20/masuk/10/00/0000_rakha_fatih_athallah_picture.jpg"
},
{
"created_at": "2022-06-16 11:23:12",
"late": "True",
"location": "Jl. H. Asmawi No.63, Beji, Kecamatan Beji, Kota Depok, Jawa Barat 16421, Indonesia",
"place": "GRIT OFFICE",
"folder": "./pegawai/grit/2022/06/16/masuk/11/23/",
"filename": "0000_rakha_fatih_athallah_picture.jpg",
"tujuan": "",
"link": "http://103.140.90.10:8081/face-recognition/client/python/pegawai/grit/2022/06/16/masuk/11/23/0000_rakha_fatih_athallah_picture.jpg"
}
],
"check_out": []
}
}
}
"""
// simulated response data
let data = jsonStr.data(using: .utf8)!
do {
self.apiResponse = try JSONDecoder().decode(Response.self, from: data)
print("\n---> apiResponse \(apiResponse)")
}
catch {
print(" error \(error)")
}
}
}
}
Swift: How to write JSON as a struct?
You could do something like this.
Firstly I converted the JSON into Data so that it is possible to decode it.
Second as there are two properties in the JSON, action and trigger, I create two nested structs that match the properties. In the second struct Trigger we need to use a custom coding key as Swift uses camelcase by convention, and you cannot use a -
in a variable name.
import Foundation
let data = """
[
{
"action": {
"type": "block"
},
"trigger": {
"url-filter": "apple.com"
}
}
]
""".data(using: .utf8)!
struct SomeName: Codable {
struct Action: Codable {
let type: String
}
struct Trigger: Codable {
let urlFilter: String
enum CodingKeys: String, CodingKey {
case urlFilter = "url-filter"
}
}
let action: Action
let trigger: Trigger
}
do {
let result = try JSONDecoder().decode([SomeName].self, from: data)
} catch {
print(error)
}
Putting the above code into a Playground, it should work.
Do I need to define the exact same structure of a JSON to use it with json.Unmarshal?
You can use a struct with fields that you only want. Look at the example below
package main
import (
"encoding/json"
"fmt"
)
type Test struct {
Field1 int `json:"field1"`
Field2 string `json:"field2"`
}
func main() {
jsonString := `{
"field1": 1,
"field2": "test field 2",
"field3": "test field 3"
}`
t := Test{}
err := json.Unmarshal([]byte(jsonString), &t)
if err != nil {
fmt.Println(err)
}
fmt.Println("\n", t)
}
https://play.golang.org/p/qFLXBiU-fMX
Do you need to create a struct with every JSON property or can you be selective? Swift
You can selectively decode only the elements of the JSON object you care about. The only requirement is that your types match the JSON objects types.
So you can safely drop the values, like you have in your example.
Swift 5 : Create JSON with Codable / Struct from my variables
As you have full controll over your structure and there is no collection involved i would recommend to put everything in one struct instead of scattering it over many different:
struct UserProfile: Codable{
var id: String
var name: String
var lastname: String
var street: String
var country: String
}
Regarding caching. As you can mark this struct Codable
it can be easily stored in Userdefaults
as Data
. This extension on Userdefaults
should allow you to access UserProfile
in a typesafe manner.
extension UserDefaults{
var userProfile: UserProfile?{
get{
guard let data = data(forKey: "userProfile") else{
return nil
}
return try? JSONDecoder().decode(UserProfile.self, from: data)
}
set{
set(try? JSONEncoder().encode(newValue), forKey: "userProfile")
}
}
}
Usage example:
//Write
UserDefaults.standard.userProfile = UserProfile(id: UUID().uuidString, name: "First", lastname: "Last", street: "nowhere", country: "atlantis")
//Read
let profile = UserDefaults.standard.userProfile
Edit:
Editing example:
As this is a struct it gets copied every time something changes.
So read the value in a var. Modify it and save it to the defaultStore.
var profile = UserDefaults.standard.userProfile
profile?.country = "newCountry"
UserDefaults.standard.userProfile = profile
Related Topics
How to Use a Variadic Closure in Swift
Swift Utf8 Encoding and Non Utf8 Character
Swift [1,2] Conforms to Anyobject But [Enum.A, Enum.B] Does Not
How to Convert Uint16 to Uint8 in Swift 3
Nsurlerrordomain with Code=-1100
Naming Convention for Optional Binding
How Are Hash Collisions Handled
Swiftui Coordinator Not Updating the Containing View's Property
Swift: Using "/" Slash in Filename with Createdirectoryatpath
Converting Swift 2.3 to Swift 3.0 - Error, Cannot Invoke 'Datatask' with an Argument List of Type'
What Happens When You Run Deprecated Code in Swift
My Uiviewcontroller Is Not Filling the Entire Screen
Any Way to Chain == and || Operands
Fblpromises Framework Not Found
Wrapping a Generic Method in a Class Extension