How to decode JSON in swift - json

{
"Meta Data": {
"1. Information": "Daily Prices (open, high, low, close) and Volumes",
"2. Symbol": "FB",
"3. Last Refreshed": "2017-08-23 16:00:00",
"4. Output Size": "Compact",
"5. Time Zone": "US/Eastern"
},
"Time Series (Daily)": {
"2017-08-23 16:00:00": {
"1. open": "168.8400",
"2. high": "169.3600",
"3. low": "168.2000",
"4. close": "168.7100",
"5. volume": "8198515"
},
"2017-08-22": {
"1. open": "168.2800",
"2. high": "169.8700",
"3. low": "167.1500",
"4. close": "169.6400",
"5. volume": "11333260"
},
"2017-08-21": {
"1. open": "167.1600",
"2. high": "168.0000",
"3. low": "165.8200",
"4. close": "167.7800",
"5. volume": "11880823"
},
"2017-08-18": {
"1. open": "166.8400",
"2. high": "168.6700",
"3. low": "166.2100",
"4. close": "167.4100",
"5. volume": "14933261"
},
"2017-08-17": {
"1. open": "169.3400",
"2. high": "169.8600",
"3. low": "166.8500",
"4. close": "166.9100",
"5. volume": "16791591"
},
"2017-08-16": {
"1. open": "171.2500",
"2. high": "171.3800",
"3. low": "169.2400",
"4. close": "170.0000",
"5. volume": "15580549"
}
}
}
My problem is how can I get all the information (ex: 4.close) from the JSON date if the days (ex: 2017-08-02) are constantly changing.
So far in my project I can only get a data of a certain date.
func fetchStockDataCalendar() {
let url = URL(string: "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=\(symbol)&apikey=\(apiKey)")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print ("ERROR")
} else {
if let content = data {
do {
//Array
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let Time = myJson["Time Series (Daily)"] as? NSDictionary {
if let Day = Time["2017-06-21"] as? NSDictionary {
if let CloseStockData = Day["4. close"] as? String {
print("2017-06-21 CloseStock-> \(CloseStockData)$")
}
}
}
} catch {
print(error.localizedDescription)
}
}
}
}
task.resume()
}

Here is your func with the above code but as an NSDictionary. My console is showing all of the closes.
2017-10-24 CloseStock-> 256.5600
2018-02-22 CloseStock-> 270.4000
func fetchStockDataCalendar() {
let url = URL(string: "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=\("SPY")&apikey=\(alphaApiKey)")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print ("ERROR")
} else {
if let content = data {
do {
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let time = myJson["Time Series (Daily)"] as? NSDictionary {
for (key, value) in time {
if let value = value as? Dictionary<String, String> {
if let close = value["4. close"] {
print("\(key) CloseStock-> \(close)")
}
}
}
}
} catch {
print(error.localizedDescription)
}
}
}
}
task.resume()
}

Use a for loop to iterate over the dictionary keys and values like this:
if let time = myJson["Time Series (Daily)"] {
for (key, value) in time {
if let close = value["4. close"] {
print("\(key) CloseStock-> \(close)")
}
}
}
Based on your comments I've added this too
if let time = myJson["Time Series (Daily)"] {
for (key, value) in time {
if let value = value as? Dictionary<String, String> {
if let close = value["close"] {
print("\(key) CloseStock-> \(close)")
}
}
}
}

Related

Trying to get the first element of JSON array Swift

I'm trying to implement a stock API. I have a JSON example:
{
"Meta Data": {
"1. Information": "Daily Prices (open, high, low, close) and Volumes",
"2. Symbol": "DAI.DEX",
"3. Last Refreshed": "2022-04-05",
"4. Output Size": "Full size",
"5. Time Zone": "US/Eastern"
},
"Time Series (Daily)": {
"2022-04-05": {
"1. open": "64.4900",
"2. high": "64.8200",
"3. low": "62.6200",
"4. close": "62.9600",
"5. volume": "3425810"
},
"2022-04-04": {
"1. open": "63.9900",
"2. high": "64.5400",
"3. low": "62.8100",
"4. close": "64.2600",
"5. volume": "2538008"
}
}
I'm trying to display the latest price so I always need the first element in Time Series Daily. In this example 2022-04-05. The list goes on for 20 years. I tried this:
var latestClose: String {
timeSeriesDaily.first?.value.close ?? ""
}
But every time I rerun the app it displays different values and not constantly the first value.
Here my Code:
struct StockData: Codable {
var metaData: MetaData
var timeSeriesDaily: [String: TimeSeriesDaily]
var latestClose: String {
timeSeriesDaily.first?.value.close ?? ""
}
private enum CodingKeys: String, CodingKey {
case metaData = "Meta Data"
case timeSeriesDaily = "Time Series (Daily)"
}
struct MetaData: Codable {
let information: String
let symbol: String
let lastRefreshed: String
let outputSize: String
let timeZone: String
private enum CodingKeys: String, CodingKey {
case information = "1. Information"
case symbol = "2. Symbol"
case lastRefreshed = "3. Last Refreshed"
case outputSize = "4. Output Size"
case timeZone = "5. Time Zone"
}
}
struct TimeSeriesDaily: Codable {
var open: String
var high: String
var low: String
var close: String
var volume: String
private enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
case volume = "5. volume"
}
}
}
There is no first element because the object is a dictionary which is unordered by definition.
To get the most recent item you have to get the dictionary keys (this returns an array) and sort them descending. The most recent date is the first item.
var latestClose: String {
guard let mostRecentDate = timeSeriesDaily.keys.sorted(by: >).first else { return "" }
return timeSeriesDaily[mostRecentDate]!.close
}

Why JSONDecoder doesn't work with a date format?

I have a simple structs to represent a JSON about a stock:
struct Share: Decodable {
enum CodingKeys: String, CodingKey {
case metaData = "Meta Data"
case prices = "Time Series (Daily)"
}
var metaData: [String: String]
var prices: [Date: TradingDay]
}
struct TradingDay: Decodable {
enum CodingKeys: String, CodingKey {
case open = "1. open"
case close = "4. close"
case high = "2. high"
case low = "3. low"
}
enum DecodingErrors: Error {
case canNotMakeDouble
case invalidCodingKeys
}
var open: Double
var close: Double
var high: Double
var low: Double
init(from decoder: Decoder) throws {
guard let container = try? decoder.container(keyedBy: CodingKeys.self) else {
throw DecodingErrors.invalidCodingKeys
}
guard let open = try? Double(container.decode(String.self, forKey: .open)),
let close = try? Double(container.decode(String.self, forKey: .close)),
let high = try? Double(container.decode(String.self, forKey: .high)),
let low = try? Double(container.decode(String.self, forKey: .low)) else {
throw DecodingErrors.canNotMakeDouble
}
self.open = open
self.close = close
self.high = high
self.low = low
}
}
JSON looks like this:
{
"Meta Data": {
"1. Information": "Daily Prices (open, high, low, close) and Volumes",
"2. Symbol": "IBM",
"3. Last Refreshed": "2022-07-01",
"4. Output Size": "Compact",
"5. Time Zone": "US/Eastern"
},
"Time Series (Daily)": {
"2022-07-01": {
"1. open": "141.0000",
"2. high": "141.6700",
"3. low": "139.2600",
"4. close": "141.1200",
"5. volume": "4012106"
},
"2022-06-30": {
"1. open": "139.5800",
"2. high": "142.4600",
"3. low": "139.2800",
"4. close": "141.1900",
"5. volume": "4878020"
},
and so on.
I'm trying to decode this with the following code:
if let (data, _) = try? await URLSession.shared.data(for: request) {
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
do {
let share = try decoder.decode(Share.self, from: data)
print(share.metaData)
print(share.prices)
} catch {
print(error.localizedDescription)
}
}
But all I see is just an error.
When I make a prices dictionary (7th row) [String: TradingDay] type, it works appropriately.
Why doesn't it decode the dates, though I passed in the date format?
Thanks everyone.

What Go tag should be used while defining struct of a JSON data if "field/key" for some values is varying or not known?

While trying to parse a time-series data I found a key field in the JSON data is the timestamp(obviously in string format). But creating a struct for the same beforehand is not possible as I cannot know the timestamp string anyway.
This is how the JSON looks like:
"Time Series (5min)": {
"2020-01-17 16:00:00": {
"1. open": "167.2000",
"2. high": "167.3400",
"3. low": "167.0100",
"4. close": "167.0500",
"5. volume": "1646699"
},
"2020-01-17 15:55:00": {
"1. open": "166.9000",
"2. high": "167.1600",
"3. low": "166.8500",
"4. close": "167.1500",
"5. volume": "622999"
},
"2020-01-17 15:50:00": {
"1. open": "166.7241",
"2. high": "166.9200",
"3. low": "166.7200",
"4. close": "166.8999",
"5. volume": "271723"
}
}
The struct for the some may look like :
type TIMESTAMP struct {
Open string `json:"1. open"`
High string `json:"2. high"`
Low string `json:"3. low"`
Close string `json:"4. close"`
Volumn string `json:"5. volumn"`
}
type TIMESERIES struct {
TimeStamp TIMESTAMP `json:""` //DON'T KNOW HOW TO IMPLEMENT THIS
}
How to handle such a situation? Is there any Go struct tag for the same?
Keys like 2020-01-17 16:00:00 seems to by dynamically generated and are not fixed, so you can use map for arbitary keys like this
package main
import (
"fmt"
"encoding/json"
)
type TIMESTAMP struct {
Open string `json:"1. open"`
High string `json:"2. high"`
Low string `json:"3. low"`
Close string `json:"4. close"`
Volumn string `json:"5. volumn"`
}
type TIMESERIES map[string]map[string]TIMESTAMP
func main() {
test := []byte(`{
"Time Series (5min)": {
"2020-01-17 16:00:00": {
"1. open": "167.2000",
"2. high": "167.3400",
"3. low": "167.0100",
"4. close": "167.0500",
"5. volume": "1646699"
},
"2020-01-17 15:55:00": {
"1. open": "166.9000",
"2. high": "167.1600",
"3. low": "166.8500",
"4. close": "167.1500",
"5. volume": "622999"
},
"2020-01-17 15:50:00": {
"1. open": "166.7241",
"2. high": "166.9200",
"3. low": "166.7200",
"4. close": "166.8999",
"5. volume": "271723"
}
}
}`)
var response TIMESERIES
if err := json.Unmarshal(test, &response); err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v", response)
}

How to convert JSON into a Go type definition when consuming an API in GO lang

I am building a an application that consumes an API then also saves the json data to go lang structs then later I will make end points that will provide results for certain calculations. I have implemented consuming the API the challenging part is how to save the data in a way that go understands. Which is a proper approach?
The following is the JSON format when I make a request.
The key I am interested in is only Time Series (1min)
JSON
{
"Meta Data": {
"1. Information": "Intraday (1min) prices and volumes",
"2. Symbol": "MSFT",
"3. Last Refreshed": "2018-05-24 16:00:00",
"4. Interval": "1min",
"5. Output Size": "Compact",
"6. Time Zone": "US/Eastern"
},
"Time Series (1min)": {
"2018-05-24 16:00:00": {
"1. open": "98.3050",
"2. high": "98.3600",
"3. low": "98.2500",
"4. close": "98.3100",
"5. volume": "2377114"
},
"2018-05-24 15:59:00": {
"1. open": "98.2900",
"2. high": "98.3300",
"3. low": "98.2900",
"4. close": "98.3000",
"5. volume": "137133"
},
"2018-05-24 15:58:00": {
"1. open": "98.2900",
"2. high": "98.3000",
"3. low": "98.2600",
"4. close": "98.2900",
"5. volume": "135875"
},
"2018-05-24 15:53:00": {
"1. open": "98.2750",
"2. high": "98.2950",
"3. low": "98.2600",
"4. close": "98.2700",
"5. volume": "77959"
}
}
}
code
package main
import (
"fmt"
"io/ioutil"
"net/http"
"github.com/jmoiron/jsonq"
)
func main() {
response, err := http.Get("https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=MSFT&interval=1min&apikey=demo")
if err != nil {
fmt.Printf("The HTTP request failed with error %s\n", err)
} else {
data, _ := ioutil.ReadAll(response.Body)
// fmt.Println(string(data))
}
}
Unfortunately, this data is poorly structured for easy unmarshaling via the golang JSON infrastructure.
The general approach for unmarshaling data such as this example is to define a type (or set of types) which contain the structure you desire and implement the json.Unmarshaler interface with logic to inspect the incoming structure and populate the desired structs manually.
For example:
type Quote struct {
Time string
Open, High, Low, Close float32
Volume int
}
type Quotes []Quote
func main() {
qs := Quotes{}
err := json.Unmarshal([]byte(jsonstr), &qs)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", qs)
}
const targetName = "Time Series (1min)"
func (qs *Quotes) UnmarshalJSON(bs []byte) error {
// Unmarshal into a generic map
obj := make(map[string]interface{})
err := json.Unmarshal(bs, &obj)
if err != nil {
return err
}
// Find the target time series
entries, ok := obj[targetName].(map[string]interface{})
if !ok {
return fmt.Errorf("cannot find entry with name %q", targetName)
}
// Parse a Quote object from each entry in the target object
quotes := []Quote{}
for timestamp, values := range entries {
values, ok := values.(map[string]interface{})
if !ok {
return fmt.Errorf("value for %q is not an object", timestamp)
}
quote := Quote{}
quote.Time = timestamp
v, err := strconv.ParseFloat(values["1. open"].(string), 32)
if err != nil {
return err
}
quote.Open = float32(v)
// Repeat for each of Close,High,Low,Volume...
quotes = append(quotes, quote)
}
*qs = Quotes(quotes)
return nil
}

JSON Swift unwrap [""]

Im having an issue unwrapping my string correctly with SwiftyJSON
output: ["AAPL"]
stockData = try Data(contentsOf: url!)
let json = try JSON(data: stockData)
if let jsonArray = json["Stock Quotes"].array {
let ticker = String(describing: jsonArray.map({$0["1. symbol"].stringValue}))
print(ticker)
}
JSON
sorry here is the full JSON
{
"Meta Data": {
"1. Information": "Batch Stock Market Quotes",
"2. Notes": "IEX Real-Time Price provided for free by IEX (https://iextrading.com/developer/).",
"3. Time Zone": "US/Eastern"
},
"Stock Quotes": [
{
"1. symbol": "AAPL",
"2. price": "168.3500",
"3. volume": "34941964",
"4. timestamp": "2018-04-06 16:55:36"
}
]
}
if let jsonArray = json["Stock Quotes"].array {
if let ticker = jsonArray[0]["1. symbol"] as? String {
print(ticker)
}
}
Can you try
if let jsonArray = json["Stock Quotes"].array {
if let ticker = jsonArray[0]["1. symbol"] as? String {
print(ticker)
}
}