How to decode some strange JSON data in Swift? - json

My JSON file is like this:
> {
"skill_list": [
{
"Skill": 78
},
{
"Skill": 57
},
"None"
]
},
{
"skill_list": [
{
"Skill": 12
},
"None",
"None"
]
},
I don't know how to define a struct to this JSON data, and I don't want to
use SwiftyJSON package.
Is there a way to define a struct to decode this JSON data?
Just like
JSONDecoder().decode(MyStruct.self, from: data)
the whole JSON is like:
{
"armor": {
"param": [
{
"skill_list": [
{
"Skill": 56
},
{
"Skill": 4
},
"None",
"None",
"None"
]
},
{
"skill_list": [
{
"Skill": 103
},
"None",
"None",
"None",
"None"
]
}
]
}
}

finally i got this, the code is :
let myJson = """
{
"armor": {
"param": [
{
"skill_list": [
{
"Skill": 56
},
"None",
"None",
"None",
"None"
]
},
{
"skill_list": [
{
"Skill": 103
},
"None",
"None",
"None",
"None"
]
}
]
}
}
"""
struct Armor: Decodable {
var armor: ArmorClass
}
struct ArmorClass: Decodable {
var param: [ArmorInfo]
}
struct ArmorInfo: Decodable {
var skill_list: [MetadataType]
}
enum MetadataType: Codable {
case Skill(SkillType)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .Skill(container.decode(SkillType.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
}
struct SkillType: Codable {
var Skill: Int
}
let jsonData = myJson.data(using: .utf8)!
let decodeData = try! JSONDecoder().decode(Armor.self, from: jsonData)
if case let MetadataType.Skill(value) = decodeData.armor.param[0].skill_list[0] {
print(value.Skill)
}
if case let MetadataType.string(value) = decodeData.armor.param[0].skill_list[1] {
print("String: \(value)")
}

Related

Decode different incoming JSON types in Swift

I'm using JSONDecoder to decode incoming websocket messages from an API. Messages come from the websockettask as a String. Right now I have my Codable struct as such:
struct JsonRPCMessage: Codable {
let jsonrpc: String
let result: String?
let method: String?
let id: Int?
}
Then I just decode it like:
let message = try decoder.decode(JsonRPCMessage.self, from: data!)
This has worked fine for about half of the endpoints in the API which just return a single String for result. The others return a dictionary. When I change the type of result to Dictionary, the struct no longer conforms to Codable. When it's left as a string, the decoder returns a type mismatch error at runtime. Plus, changing the type to dictionary would break functionality for the rest of the api's features.
Looking for ideas to decode and access the string to value pairs in that dictionary as well as check for dictionary or string before sending it to the decoder.
Here are some samples of the different types of response I need to be able to sort and parse:
{
"jsonrpc": "2.0",
"result": {
"klippy_connected": true,
"klippy_state": "ready",
"components": [
"klippy_connection",
"history",
"octoprint_compat",
"update_manager"
],
"failed_components": [],
"registered_directories": [
"config",
"logs",
"gcodes",
"config_examples",
"docs"
],
"warnings": [],
"websocket_count": 4,
"moonraker_version": "v0.7.1-659-gf047167",
"missing_klippy_requirements": [],
"api_version": [1, 0, 5],
"api_version_string": "1.0.5"
},
"id": 50
}
{
"jsonrpc": "2.0",
"method": "notify_proc_stat_update",
"params": [
{
"moonraker_stats": {
"time": 1663016434.5099802,
"cpu_usage": 0.74,
"memory": 35716,
"mem_units": "kB"
},
"cpu_temp": null,
"network": {
"lo": { "rx_bytes": 2568, "tx_bytes": 2568, "bandwidth": 0.0 },
"tunl0": { "rx_bytes": 0, "tx_bytes": 0, "bandwidth": 0.0 },
"ip6tnl0": { "rx_bytes": 0, "tx_bytes": 0, "bandwidth": 0.0 },
"eth0": {
"rx_bytes": 2529302,
"tx_bytes": 13891023,
"bandwidth": 7005.14
}
},
"system_cpu_usage": {
"cpu": 25.62,
"cpu0": 1.98,
"cpu1": 1.0,
"cpu2": 0.0,
"cpu3": 100.0
},
"system_memory": {
"total": 8039920,
"available": 7182640,
"used": 857280
},
"websocket_connections": 4
}
]
}
{
"jsonrpc": "2.0",
"result": "ok",
"id": 50
}
In this case, the better option is to receive the same JSON in each case, but if you can't control that then you can implement custom decoding using init(from:).
struct JsonRPCMessage: Decodable {
enum CodingKeys: String, CodingKey {
case jsonrpc, result, method, id
}
let jsonrpc: String
let result: String?
let method: String?
let id: Int?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.jsonrpc = try container.decode(String.self, forKey: .jsonrpc)
if let dic = try container.decodeIfPresent([String: Any].self, forKey: .result) {
self.result = dic["YourKey"] as? String
}
else {
self.result = try container.decodeIfPresent(String.self, forKey: .result)
}
// If you have a custom type for Result than
if let result = try container.decodeIfPresent(YourResultType.self, forKey: .result) {
self.result = result.propertyOfYourResult
}
else {
self.result = try container.decodeIfPresent(String.self, forKey: .result)
}
self.method = try container.decodeIfPresent(String.self, forKey: .method)
self.id = try container.decodeIfPresent(Int.self, forKey: .id)
}
}
You're trying to force 3 different JSON shapes into the same Swift struct. This is generally not advised. You could do custom decoding as #Nirav suggested, but then you're essentially adding business logic decoding to your models and this will quickly grow out of hand and be untestable.
I believe a far better solution is to create 3 different struct and try to decode one, if not, try decoding the other, etc... and handle any error as appropriate, and test this behaviour:
import Foundation
import SwiftUI
let json1 = """
{
"jsonrpc": "2.0",
"result": {
"klippy_connected": true,
"klippy_state": "ready",
"components": [
"klippy_connection",
"history",
"octoprint_compat",
"update_manager"
],
"failed_components": [],
"registered_directories": [
"config",
"logs",
"gcodes",
"config_examples",
"docs"
],
"warnings": [],
"websocket_count": 4,
"moonraker_version": "v0.7.1-659-gf047167",
"missing_klippy_requirements": [],
"api_version": [1, 0, 5],
"api_version_string": "1.0.5"
},
"id": 50
}
""".data(using: .utf8)!
struct JSON1: Codable {
var jsonrpc: String
var result: JSONResult
var id: Int
struct JSONResult: Codable {
var klippy_connected: Bool
var klippy_state: String
var components: [String]
var failed_components: [String]
var registered_directories: [String]
// etc...
}
}
let json2 = """
{
"jsonrpc": "2.0",
"method": "notify_proc_stat_update",
"params": [
{
"moonraker_stats": {
"time": 1663016434.5099802,
"cpu_usage": 0.74,
"memory": 35716,
"mem_units": "kB"
},
"cpu_temp": null,
"network": {
"lo": { "rx_bytes": 2568, "tx_bytes": 2568, "bandwidth": 0.0 },
"tunl0": { "rx_bytes": 0, "tx_bytes": 0, "bandwidth": 0.0 },
"ip6tnl0": { "rx_bytes": 0, "tx_bytes": 0, "bandwidth": 0.0 },
"eth0": {
"rx_bytes": 2529302,
"tx_bytes": 13891023,
"bandwidth": 7005.14
}
},
"system_cpu_usage": {
"cpu": 25.62,
"cpu0": 1.98,
"cpu1": 1.0,
"cpu2": 0.0,
"cpu3": 100.0
},
"system_memory": {
"total": 8039920,
"available": 7182640,
"used": 857280
},
"websocket_connections": 4
}
]
}
""".data(using: .utf8)!
struct JSON2: Codable {
var jsonrpc: String
var params: [JSONParams]
var method: String
struct JSONParams: Codable {
var moonraker_stats: MoonrakerStats
// etc...
struct MoonrakerStats: Codable {
var time: Double
var cpu_usage: Double
// etc...
}
}
}
let json3 = """
{
"jsonrpc": "2.0",
"result": "ok",
"id": 50
}
""".data(using: .utf8)!
struct JSON3: Codable {
var jsonrpc: String
var result: String
var id: Int
}
let data = [json1, json2, json3].randomElement()!
let decoder = JSONDecoder()
if let decoded = try? decoder.decode(JSON1.self, from: data) {
print("we have json 1")
print(decoded)
} else if let decoded = try? decoder.decode(JSON2.self, from: data) {
print("we have json 2")
print(decoded)
} else if let decoded = try? decoder.decode(JSON3.self, from: data) {
print("we have json 3")
print(decoded)
} else {
print("we don't know what we have")
}

fetch api data with dataclass

I've given an API with dataclass, I want to fetch the dataclass, I tried to search up on internet but coulnd't find the answer, after that I tried to fetch it with my own, but my code doesn't seem to work. any ideas ?
"restricions": {
"restrictionsByNationality": [
{
"type": "Visit Type",
"data": {
"allowsTourists": true,
"allowsBusinessVisit": true
}
},
{
"type": "Covid Test",
"data": {
"pcrRequired": true,
"fastTestRequired": true
}
},
{
"type": "Documents Required",
"data": {
"biometricPassportRequired": true,
"locatorFormRequired": true,
"covidPassportRequired": false
}
}
]
},
if let nationality = data["restrictionsByNationality"] as? [String: Any]{
nationality = DataClass(from: nationality)
}
struct RestrictionsByNationality: Codable {
let type: String
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let allowsTourists,
allowsBusinessVisit, pcrRequired, fastTestRequired: Bool?
let biometricPassportRequired, locatorFormRequired, covidPassportRequired: Bool?
}
}

How to filter from two JSON fields at the same time (SwiftUI)

I'm trying to filter data from two JSON fields at the same time depending on the system language: at this point, system language recognition seems to work (because different elements appears according to the system language), but for some reasons I can't find the exact code to do what I want to do...
As you can see from the code, I first tried to filter the field "data" for a specific word ("italiano" or "english"), then the field "autore" to categorize the various informations...but the results are not good: sometimes I see the same information repeated in the list, other times I only see the informations filtered from the "data" field but not from the "autore" field...
So, in conclusion, the result I would like to obtain is the following: if the system language is Italian, all the JSON data containing the keyword "italian" in the "data" field must be filtered first, and then filtered further based on the specific keyword contained in the "autore" field; if, on the other hand, the system language is not Italian (but any other language), all the JSON data containing the keyword "english" in the "data" field must be filtered first, and then - as in the previous case - filtered further based on the specific keyword contained in the "autore" field.
Do you have any advice? Because I think I'm not very far from the exact code...or maybe not :)
Thank you!
Here's the SwiftUI code:
import SwiftUI
import URLImage
struct HistoryView: View {
#ObservedObject var dm: DownloadManager
let sysLanguage = NSLocale.current.languageCode
var body: some View {
if sysLanguage == "it" {
List {
ForEach(dm.JSON.filter {
$0.data == "italiano"
}) { busso in
ForEach(dm.JSON.filter {
$0.autore == "storia"
}) { busso in
NavigationLink(
destination: DetailView(busso: busso)) {
HStack {
URLImage(URL(string: busso.fotoUrl) ?? furl)
.resizable()
.aspectRatio(contentMode: .fit)
Text(busso.titolo)
.font(.headline)
Spacer().layoutPriority(-0.1)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 50)
}
}
}
}
.navigationTitle(Text("Storia di Busso"))
.navigationBarTitleDisplayMode(.large)
} else {
List {
ForEach(dm.JSON.filter {
$0.autore == "storia"
}) { busso in
ForEach(dm.JSON.filter {
$0.data == "english"
}) { busso in
NavigationLink(
destination: DetailView(busso: busso)) {
HStack {
URLImage(URL(string: busso.fotoUrl) ?? furl)
.resizable()
.aspectRatio(contentMode: .fit)
Text(busso.titolo)
.font(.headline)
Spacer().layoutPriority(-0.1)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 50)
}
}
}
}
.navigationTitle(Text("Storia di Busso"))
.navigationBarTitleDisplayMode(.large)
}
}
}
struct HistoryView_Previews: PreviewProvider {
static var previews: some View {
HistoryView(dm: DownloadManager())
}
}
Here's the JSON file:
[
{
"id": "8",
"titolo": "View",
"autore": "galleria",
"testo": "",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-13 22:55:57",
"foto": "foto\/WP_20161110_001.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/WP_20161110_001.jpg"
},
{
"id": "7",
"titolo": "Storia di Busso",
"autore": "storia",
"testo": "Testo di prova",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-10 21:11:03",
"foto": "foto\/1a3e733334ec8948b0328af4e5b7288a.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/1a3e733334ec8948b0328af4e5b7288a.jpg"
},
{
"id": "6",
"titolo": "Test 2",
"autore": "ricette",
"testo": "",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-08 10:49:56",
"foto": "foto\/test_2.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/test_2.jpg"
},
{
"id": "5",
"titolo": "Test",
"autore": "eventi",
"testo": "",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-08 10:47:53",
"foto": "foto\/coastal-wash-web.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/coastal-wash-web.jpg"
},
{
"id": "4",
"titolo": "Immagine di prova",
"autore": "luoghi",
"testo": "",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-08 10:24:46",
"foto": "foto\/unnamed.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/unnamed.jpg"
},
{
"id": "3",
"titolo": "Panorama",
"autore": "galleria",
"testo": "",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-07 11:21:53",
"foto": "foto\/WP_20161110_001.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/WP_20161110_001.jpg"
},
{
"id": "2",
"titolo": "Comune di Busso",
"autore": "contatti",
"testo": "Indirizzo, telefono, mail, altri dati da inserire",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-01 19:33:56",
"foto": "foto\/DSCN0914.JPG",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/DSCN0914.JPG"
},
{
"id": "1",
"titolo": "Chiesa",
"autore": "commercio",
"testo": "Testo di prova, abbastanza lungo per verificare l'impaginazione e correggere eventuali errori.",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-01 19:32:02",
"foto": "foto\/CAM_0044.JPG",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/CAM_0044.JPG"
}
]
Here's the DownloadManager code:
import SwiftUI
import Combine
class DownloadManager: ObservableObject {
#Published var JSON: [BussoModel] = []
#Published var searchText: String = "" {
didSet {
self.searchResults = self.JSON.filter { $0.titolo.contains(self.searchText) }
}
}
#Published var searchResults: [BussoModel] = []
init() {
let url = URL(string: "https://geniuspointfrezza.altervista.org/index.php?json=1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let bussoData = data {
let decodedData = try JSONDecoder().decode([BussoModel].self, from: bussoData)
DispatchQueue.main.async {
self.JSON = decodedData
}
} else {
print("No data")
}
} catch {
print(error)
}
}.resume()
}
}
Based on your comments, I believe this is what you're trying to do (unless I misunderstood something). I removed the URL image from the code, so you'll have to add it back.
A couple notes:
Try to do all filtering and data management within the DownloadManager. I added a filter function, which is called when the data gets downloaded and also when the view gets initialized.
Try to avoid hard coding strings into your code. I created a Language enum that will handle the "english" and "italian" filter.
If you ever run into a situation in your code where you're duplicating a whole section (like in your post you rewrote the view for "it" and "else"), then there's definitely a better way to do it.
.
import SwiftUI
//import URLImage
struct HistoryView: View {
#ObservedObject var dm: DownloadManager
let title: String
init(dm: DownloadManager, autore: String) {
self.dm = dm
dm.filter(autore: autore)
self.title = "\(autore)".capitalized + " di Busso"
}
var body: some View {
List {
if !dm.searchResults.isEmpty && !dm.isLoading {
ForEach(dm.searchResults) { busso in
NavigationLink(
destination: Text(busso.titolo)) {
HStack {
Text(busso.fotoUrl)
Text(busso.titolo)
.font(.headline)
Spacer(minLength: 0)
}
.frame(maxWidth: .infinity)
.frame(height: 50)
}
}
} else if dm.isLoading {
ProgressView()
} else {
Text("No results.")
}
}
.navigationTitle(title)
}
}
struct HistoryView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
HistoryView(dm: DownloadManager(), autore: "galleria")
}
}
}
import SwiftUI
import Combine
class DownloadManager: ObservableObject {
#Published private(set) var JSON: [BussoModel] = []
#Published private(set) var isLoading: Bool = true
#Published private(set) var searchAutore: String?
#Published private(set) var searchResults: [BussoModel] = []
let language: Language
enum Language: String {
case italian
case english
}
init() {
language = NSLocale.current.languageCode == "it" ? .italian : .english
getData()
}
private func getData() {
let url = URL(string: "https://geniuspointfrezza.altervista.org/index.php?json=1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let bussoData = data {
let decodedData = try JSONDecoder().decode([BussoModel].self, from: bussoData)
DispatchQueue.main.async {
self.JSON = decodedData
self.isLoading = false
self.filter(autore: self.searchAutore)
}
} else {
print("No data")
self.isLoading = false
}
} catch {
print(error)
self.isLoading = false
}
}.resume()
}
func filter(autore: String?) {
searchAutore = autore
searchResults = JSON.filter({ (bussoModel) -> Bool in
return bussoModel.data == language.rawValue && (bussoModel.autore == searchAutore)
})
}
}
struct BussoModel: Codable, Identifiable {
let id: String
let titolo: String
let autore: String
let testo: String
let data: String
let extra1: String
let extra2: String
let creazione: String
let foto: String
let fotoUrl: String
}

Swift: search for json key and edit it

I writing because I need to search a json-key passed in a function like a string. Do you have any suggestion on how I could implement it? Once I find the key I also need to edit the value. Here there is the code I wrote until now:
JSON:
{
"JSONRoot": {
"version": 1,
"Town": {
"hour": 0,
"latitude": "",
"longitude": 0,
"latitudine": 0
},
"MeasurePoints": {
"MeasurePoint": [
{
"code": "",
"codelocation": "",
}
]
},
"Wakeup": {
"startH": 6,
"startM": 0,
"maxAttempts": 3,
"maxRetry": 10
},
"Config": {
"port": 12345,
"A": {
"writable": true,
"value": 12
},
"B": {
"writable": true,
"value": 8
},
},
"Sales": {
"Stores": {
"Store": [
{
"description": "A description",
"type": "1",
"Floors": {
"basement": true,
"number": 2
},
"Doors": {
"type": "",
"number": 7
},
"Lights": {
"number": 20
}
},
{
"description": "A description",
"type": "4",
"Floors": {
"basement": none,
"number": 1
},
"Doors": {
"type": "",
"number": 4
},
"Lights": {
"number": 8
}
}
]
}
}
}
}
Structs with codable:
// MARK: - JSONConfig
struct JsonConfig: Codable {
let jsonRoot: JSONRoot?
enum CodingKeys: String, CodingKey {
case jsonRoot = "JSONRoot"
}
}
// MARK: - JSONRoot
struct JSONRoot: Codable {
let version: Int?
let measurePoints: MeasurePoints?
let wakeup: Wakeup?
let config: Config?
let sale: Sale?
enum CodingKeys: String, CodingKey {
case version
case measurePoints = "MeasurePoints"
case wakeup = "Wakeup"
case config = "Config"
case sale = "Sale"
}
}
// MARK: - Stores
struct Stores: Codable {
let stores: [Store]?
enum CodingKeys: String, CodingKey {
case stores = "Stores"
}
}
// MARK: - Store
struct Store: Codable {
let storeDescription: String?
let type: Int?
let floors: Floors?
let doors: Doors?
let lights: Lights?
enum CodingKeys: String, CodingKey {
case storeDescription = "description"
case type
case floors = "Floors"
case doors = "Doors"
case lights = "Lights"
}
}
// MARK: - Floors
struct Floors: Codable {
let basement: Bool?
let number: Int?
}
// MARK: - Doors
struct Doors: Codable {
let type: String?
let number: Int?
}
// MARK: - Lights
struct Lights: Codable {
let number: Int?
}
// MARK: - MeasurePoints
struct MeasurePoints: Codable {
let measurePoint: [MeasurePoint]?
enum CodingKeys: String, CodingKey {
case measurePoint = "MeasurePoint"
}
}
// MARK: - MeasurePoint
struct MeasurePoint: Codable {
let code, codeLocation: String?
}
// MARK: - Config
struct Config: Codable {
let port: Int?
let a, b: K?
enum CodingKeys: String, CodingKey {
case port
case a = "A"
case b = "B"
}
}
// MARK: - K
struct K: Codable {
let writable: Bool?
let value: Int?
}
// MARK: - Wakeup
struct Wakeup: Codable {
let startH, startM, maxAttempts, maxRetry: Int?
}
Function to search for a key:
func setKeyValue(jsonKey: String, value: String) {
let decoder = JSONDecoder()
let jsonData = Data(C.jsonString.utf8)
if let jsonResult = try? decoder.decode(JsonConfig.self, from: jsonData) {
// At this point I have the jsonKey = "JSONRoot.Wakeup.maxRetry" but how I can use it to search for
// the key in the jsonResult?
}
}
Obviously I need to create a new struct to edit the json but one step at a time.
Using JSONSerialisation is probably the most straightforward way here
var value: Any?
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
let keys = "JSONRoot.Wakeup.maxRetry".split(separator: ".").map {String($0)}
var dict = jsonResult
for i in 0..<keys.count {
if let temp = dict[keys[i]] as? [String:Any] {
dict = temp
continue
}
value = dict[keys[i]]
}
}
} catch {
print(error)
}
Note that this doesn't support arrays but a solution for that is very dependent on how the search key syntax would handle an array
If my thinking is correct as you, you can try with this code.
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = """
{
"JSONRoot": {
"version": 1,
"Town": {
"hour": 0,
"latitude": "",
"longitude": 0,
"latitudine": 0
},
"MeasurePoints": {
"MeasurePoint": [{
"code": "",
"codelocation": ""
}]
},
"Wakeup": {
"startH": 6,
"startM": 0,
"maxAttempts": 3,
"maxRetry": 10
},
"Config": {
"port": 12345,
"A": {
"writable": true,
"value": 12
}
},
"Sales": {
"Stores": {
"Store": [{
"description": "A description",
"type": "1",
"Floors": {
"basement": true,
"number": 2
},
"Doors": {
"type": "",
"number": 7
},
"Lights": {
"number": 20
}
},
{
"description": "A description",
"type": "4",
"Floors": {
"basement": "none",
"number": 1
},
"Doors": {
"type": "",
"number": 4
},
"Lights": {
"number": 8
}
}
]
}
}
}
}
"""
editJson(jsonString)
}
func editJson(_ jsonString: String) {
do{
let jsonData = Data(jsonString.utf8)
var jsonObject = try JSONSerialization.jsonObject(with: jsonData)
parseDict(&jsonObject)
print("jsonObject: \(String(describing: jsonObject))")
}catch let error {
print(error.localizedDescription)
}
}
func parseDict(_ jsonObject: inout Any) {
if let _ = jsonObject as? String {
return
} else if var dictionary = jsonObject as? Dictionary<String, Any> {
for (key, value) in dictionary {
var nextObject = value
parseDict(&nextObject)
if let value = getValueWith(key), let _ = dictionary.removeValue(forKey: key) {
dictionary[key] = value
} else {
dictionary[key] = nextObject
}
}
jsonObject = dictionary
}else if let array = jsonObject as? Array<Any> {
var updatedArray = array
for (index, value) in array.enumerated() {
var nextObject = value
parseDict(&nextObject)
updatedArray[index] = nextObject
}
jsonObject = updatedArray
}
}
func getValueWith(_ key: String) -> String? {
return [
"description" : "Amit (amitpstu1#gmail.com) ... so on"
][key]
}
You can refresh your memory or learn more here:
https://developer.apple.com/documentation/foundation/archives_and_serialization/using_json_with_custom_types
You would be looking at merge json from different depths section. Using encodable extension etc.
You could also look here: In Swift, can one use a string to access a struct property? If you want to roll your own search function, like a modified dfs or something.

serder_json heserialize map with value being Number or boolean or String

I've been trying to deserialize a JSON containing an object which values can only be a String, a Boolean or a Number.
Here is the code I use for that:
use serde::{Deserialize, Serialize};
use serde_json::Number;
use std::collections::HashMap;
#[derive(Deserialize, Debug, Serialize, Clone)]
pub enum OtherValue {
Bool(bool),
Number(Number),
Text(String),
}
#[derive(Deserialize, Debug, Serialize)]
pub struct Item {
numbers: HashMap<String, Number>,
others: HashMap<String, OtherValue>,
}
Here are the tests I put in place if you want to try it
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_with_numbers() {
let input_str = r#"
{
"numbers": {
"key": 12.34
},
"others": {}
}
"#;
let output = serde_json::from_str::<Item>(input_str).unwrap();
assert_eq!(output.numbers.keys().len(), 1);
assert_eq!(output.others.keys().len(), 0);
}
#[test]
fn test_deserialize_with_strings() {
let input_str = r#"
{
"numbers": {
"key": "trololo"
},
"others": {}
}
"#;
let output = serde_json::from_str::<Item>(input_str);
assert_eq!(output.is_err(), true);
}
#[test]
fn test_deserialize_with_boolean_field() {
let input_str = r#"
{
"numbers": {
"key": true
},
"others": {}
}
"#;
let output = serde_json::from_str::<Item>(input_str);
assert_eq!(output.is_err(), true);
}
#[test]
fn test_deserialize_with_array_field() {
let input_str = r#"
{
"numbers": {
"key": ["trollo", true, 42]
},
"others": {}
}
"#;
let output = serde_json::from_str::<Item>(input_str);
assert_eq!(output.is_err(), true);
}
#[test]
fn test_deserialize_with_number_tag() {
let input_str = r#"
{
"others": {
"key": 42
},
"numbers": {}
}
"#;
let output = serde_json::from_str::<Item>(input_str).unwrap();
assert_eq!(output.others.contains_key("key"), true);
}
#[test]
fn test_deserialize_with_string_tag() {
let input_str = r#"
{
"others": {
"key": "trololo"
},
"numbers": {}
}
"#;
let output = serde_json::from_str::<Item>(input_str).unwrap();
assert_eq!(output.others.contains_key("key"), true);
}
#[test]
fn test_deserialize_with_boolean_tag() {
let input_str = r#"
{
"others": {
"key": true
},
"numbers": {}
}
"#;
let output = serde_json::from_str::<Item>(input_str).unwrap();
assert_eq!(output.others.contains_key("key"), true);
}
}
But apparently that doesn't work properly, I cannot deserialize the OtherValue.
Do you have any idea of how I could do that?
Here is a playground if you want to try it https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=67b43cfd4d12efc6641f5b7fba02889a
Tag your enum with #[serde(untagged)]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=85e182f1078025604e3ffbaa4adc7b2e