Trying to parse Json

Hi,
i’m trying to wrap my head around Swift 4 JSONDecoder but it feels like i’m running in circles. I have this json (just a snippet):

	"approvedTime": "2018-02-08T18:02:25Z",
	"referenceTime": "2018-02-08T18:00:00Z",
	"geometry": {
		"type": "Point",
		"coordinates": [
			[
				16.178786,
				58.573639
			]
		]
	},
	"timeSeries": [
		{
			"validTime": "2018-02-08T19:00:00Z",
			"parameters": [
				{
					"name": "spp",
					"levelType": "hl",
					"level": 0,
					"unit": "percent",
					"values": [
						-9
					]
				},
				{
					"name": "pcat",
					"levelType": "hl",
					"level": 0,
					"unit": "category",
					"values": [
						0
					]
				},

What i want is to get the values from parameters. Is it someone who can help me with some code so i can move forward.

import UIKit

let json = """
{
    "approvedTime": "2018-02-08T18:02:25Z",
    "referenceTime": "2018-02-08T18:00:00Z",

    "geometry": {
        "type": "Point",
        "coordinates": [
            [
                16.178786,
                58.573639
            ]
        ]
    },

    "timeSeries": [
        {
            "validTime": "2018-02-08T19:00:00Z",
            "parameters": [
                {
                    "name": "spp",
                    "levelType": "hl",
                    "level": 0,
                    "unit": "percent",
                    "values": [-9]
                },
                {
                    "name": "pcat",
                    "levelType": "hl",
                    "level": 0,
                    "unit": "category",
                    "values": [0]
                }
            ]
        }
    ]
}
"""

if let jsonData = json.data(using: .utf8) {
    if let jsonObj = try? JSONSerialization.jsonObject(with: jsonData, options: []) {
        let jsonDict = jsonObj as! [String: Any]
        let timeSeriesArray = jsonDict["timeSeries"] as! [[String: Any]]
        let paramsArray = timeSeriesArray[0]["parameters"] as! [[String: Any]]
        let firstDict = paramsArray[0]
        
        print(firstDict["name"] as! String)
        for value in paramsArray[0].values {
            print(value)
        }
    }
    else {
        print("Not valid json.")
    }
}
else {
    print("Found non UTF-8 characters in json string.")
}

--output:--
spp

spp
0
percent
(
    "-9"
)
hl

You can also use optional chaining:

let name = ((((jsonDict["timeSeries"] as? [[String: Any]])?[0]["parameters"]) as? [[String: Any]])?[0]["name"])!
print(name)

--output:--
spp

Nice! lol. Now, using the SwiftyJSON pod:

let swiftyJSON = JSON(jsonData)

if let name = swiftyJSON["timeSeries"][0]["parameters"][0]["name"].string {
    print(name)
}
else {
    print("no val")
}

let path: [JSONSubscriptType] = ["timeSeries", 0, "parameters", 0, "name"]

if let name = swiftyJSON[path].string {
    print(name)
}

--output:--
spp
spp
1 Like

Hi @7stud,
Prior to the Swift4 JSONDecoder, the only way was to use JSON Serializer.
Now, that we have JSONDecoder, it maps the data to classes or structures, maps the data for you.

Hi @milhouse,
In order to use Swift4, you need to create a structure or class to hold the JSON data that will be extracted. In the case of the snippet you posted, there are a couple of things, you can create a single structure with all of the members and sub members or use composition to create a more complex structure.

Here are some links to tutorials on this site that provide details
https://www.raywenderlich.com/172145/encoding-decoding-and-serialization-in-swift-4

and a video that explains the same at https://videos.raywenderlich.com/screencasts/1249-what-s-new-in-foundation-parsing-json-in-swift-4

Lastly, if code is what you are after, here’s a sample

struct Parameters: Codeable {
    var name: String 
    var levelType: String 
    var level: Int 
    var unit: String 
    var values: [Int]
}

struct TimeSeries Codeable {
    var validTime: Date
   var parameters: [Parameters]
}

Then when you get the json as Data (using contents of a local file or downloaded using any way or downloading data) you can simply decode the JSON directly as

// data is the JSON in Data format encoded
let myTimeSeries = JSONDecoder().decode(TimeSeries.self, from: data)

What this does is creates an object from the structure TimeSeries and sets the relevant fields, this is called automatic JSON Decoding.

Note: You will encounter some issues with the Date Format and structure. Suggestion: Start with the smallest and basic json to get your head around it all, then add more levels and structures, have fun.

cheers,

Jayant

1 Like

Neat. Thanks for the JSONDecoder example. There are some errors in the code you posted. I think it should be something like:

    if let jsonData = json.data(using: .utf8) {
        if let timeSeries = try? JSONDecoder().decode(TimeSeries.self, from: jsonData) {
            print(timeSeries.parameters)
        }
        else {
            print("timeSeries was nil")
        }
    }
    else {
        print("Found non UTF-8 characters.")
    }

But here is my question: the op’s json is actually more complicated than your structs indicate because timeSeries itself is a key in a larger dictionary. Doesn’t that mean you have to define yet another struct with timeSeries being a property of that struct? If so, then things seem to get just as unwieldy as with JSONSerialization in Swift 3 when you have deeply nested json. Also, I can imagine a top level json dictionary with 1,000 keys. Would the struct also need 1,000 properties?

Given all that, is SwiftyJSON a better solution? Uhg. I’m having no luck getting a JSONDecoder example to work on the op’s JSON.

Success! You can actually pick out the keys that you are interested in, so if the json has 1,000 keys you don’t need to define a struct with 1,000 properties.

struct Parameters: Codable {
    var name: String
    var levelType: String
    var level: Int
    var unit: String
    var values: [Int]
}

struct TimeSeries: Codable {
    var parameters: [Parameters]  //Pick out only one key from the timeSeries dict.
}

struct Outer: Codable {
    var timeSeries: [TimeSeries]  //Pick out only one key from the top level dict.
}

Inside some method:

        let json = """
        {
            "approvedTime": "2018-02-08T18:02:25Z",
            "referenceTime": "2018-02-08T18:00:00Z",

            "geometry": {
                "type": "Point",
                "coordinates": [
                    [
                        16.178786,
                        58.573639
                    ]
                ]
            },

            "timeSeries": [
                {
                    "validTime": "2018-02-08T19:00:00Z",
                    "parameters": [
                        {
                            "name": "spp",
                            "levelType": "hl",
                            "level": 0,
                            "unit": "percent",
                            "values": [-9]
                        },
                        {
                            "name": "pcat",
                            "levelType": "hl",
                            "level": 0,
                            "unit": "category",
                            "values": [0]
                        }
                    ]
                }
            ]
        }

        """

        if let jsonData = json.data(using: .utf8) {
            if let outer = try? JSONDecoder().decode(Outer.self, from: jsonData) {
                print(outer.timeSeries[0].parameters[0].name)
            }
            else {
                print("outer was nil: either from invalid json or structs not matching the json")
            }
        }
        else {
            print("Found non UTF-8 characters in json string.")
        }

You can do the same thing with SwiftyJSON, and you don’t have to go through all the trouble of defining the three structs. I do see how JSONDecode would be valuable in the right situation.

1 Like

@7stud,

I’m having no luck getting a JSONDecoder example to work on the op’s JSON.

That is complicated hence I suggested to @milhouse that he should attempt working with a simpler JSON to understand how JSONDecoder works.

Thanks for spotting, I got the parameter mixed, I am correcting my response above.

Lastly, is SwiftyJSON or any other such library useful or better, I agree with you that parsing some JSON structures are not easy or are too much effort however, it could also be that not all JSON structures are properly structured. XML had better documentation, JSON did not. Using JSONDecoder, developers might have more inputs into the JSON structure and it could stem from the structures/classes. So till then whatever works and resolves the problem.

cheers,

Jayant

1 Like

This is a minor detail, but if you are simply parsing JSON from the server, and not sending data up to the server, you don’t even have to implement Codable, implementing Decodable would suffice.

Not trying to be pedantic :slight_smile:

Hi @syedfa,
I cannot get my head around how that helps resolve parsing a complex JSON. I believe the original problem was to use Swift4’s JSONDecoder to get a particular field in a very complexly organised JSON format. While what you have said is factually correct but it does not help towards resolving the question. I think that having Codable instead of individually Encodable or Decodable is not bad as it does not take up extra space or memory and for a beginner to intermediate programmer, helps them get out of sticky situations while working with JSON.

Sometimes we sway the topic away from the original question.

Cheers,

Jayant

I apologize for any confusion that I caused, my intention was not to step on your toes, nor to divert the focus of the thread. I was very clear in what I said in my comment that it was a minor detail, and that I wasn’t trying to be pedantic. I did not say at any point, nor imply that this detail would somehow improve JSON parsing. I merely pointed it out when I noticed that the conversation was exclusively about JSON data being received from the server. For this reason only, I mentioned it as simply a side detail.

With respect to your responses earlier, I actually prefer to use Swift 4’s Codable protocol to parse JSON as opposed to using any third party libraries. I feel that even in cases where I have to create multiple model objects for very deep JSON data sent from the server, the edge cases will always exist, and for most REST API’s, the Codable protocol is sufficient. My personal preference is to always use native API’s unless absolutely necessary.

Once again, I apologize if you feel that I disrupted the flow of the conversation in any way.

Hi @syedfa,
Maybe the intent of my response came out a bit stronger than I might have intended. There are no toes to step on, so it’s all ok.

In regards to the JSON snippet, it is so easy to create and parse that with Javascript but when you try to structure it with ProtoBuffers or Swift, it gets a little difficult. In fact the JSON structure might require a refactor for it to be parsed with Swift.

Cheers,

Jayant

This topic was automatically closed after 166 days. New replies are no longer allowed.