Parsing JSON with Mantle - Swift - json

I'm using mantle to parse JSON data with Swift. The content of the JSON file consists of:
{
"Name1": [
{
"Type": "New",
"Available": true,
"Kind": "4178228729",
"Loot": "4367",
"Advanced": [
{
"Type": "Old",
"Name": "RoundRobin",
"Available": true,
"Specs": [
{
"Type": "Fire",
"Available": true,
"Actions": [
--continues with similar pattern--
],
"Name2": [
--repeats the same pattern at before--
]
}
I created the model classes, inheriting from MTLJSONSerializing. The "first level class" is to contain the two top level arrays of objects.
import Foundation
class lv1Class: MTLJSONSerializing {
let name1: Array<lv2Class> = []
let name2: Array<lv2Class> = []
class func name2JSONTransformer() -> NSValueTransformer {
return NSValueTransformer.mtl_JSONArrayTransformerWithModelClass(lv2Class.self)
}
class func name1JSONTransformer() -> NSValueTransformer {
return NSValueTransformer.mtl_JSONArrayTransformerWithModelClass(lv2Class.self)
}
override class func JSONKeyPathsByPropertyKey() -> [NSObject : AnyObject]!
{
return ["name1": "Name1",
"name2": "Name2"]
}
}
And it goes on like this deeper and deeper.
import Foundation
class lv2Class: MTLJSONSerializing {
let type: String = ""
let available: Bool = true
let kind: String = ""
let loot: String = ""
let advanced: Array<lv3Class> = []
class func advancedJSONTransformer() -> NSValueTransformer {
return NSValueTransformer.mtl_JSONArrayTransformerWithModelClass(lv3Class.self)
}
override class func JSONKeyPathsByPropertyKey() -> [NSObject : AnyObject]!
{
return ["type": "Type",
"available": "Available",
"kind": "Kind",
"loot": "Loot",
"advanced": "Advanced"]
}
}
The other classes are similar in structure. (I can post them all on gists if necessary.)
The call to get the JSON data is as follows:
if let lv1ClassObject = MTLJSONAdapter.modelOfClass(lv1Class.self, fromJSONDictionary: testDic, error: errorjson) as? lv1Class
testDic is the file posted above.
The call that i make to get the json data succeeds, I'm pretty confident that the problem is in my data model. The value of the dictionary is an array so I feel pretty confident parsing it as an array.
The error I get is:
Assertion failure in -[MTLJSONAdapter initWithJSONDictionary:modelClass:error:], /Users/xx/Documents/Xcode/My_project/Pods/Mantle/Mantle/MTLJSONAdapter.m:149
2015-03-14 14:34:20.331 My_project[25794:711328] *** Caught exception available is not a property of My_project.lv2Class
But available is a property of such class...
let available: Bool = true
Hope it's clear enough, if you have any question feel free to ask.
I'm really not familiar with parsing JSON, thanks everyone for the help.

Answer is here.
You both need to put them as dynamic and to make sure that all your property can be bridged to the equivalent Obj-C property.

Related

Deep remove specific empty json array in circe scala

I want to remove deep empty json array from my json before/during processing by circe.
Incoming JSON
{
"config": {
"newFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"oldFiles": [],
"channel": "BC"
}
}
or
{
"config": {
"newFiles": [],
"oldFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
Resulted Json should look like
{
"config": {
"newFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
or
{
"config": {
"oldFiles": [{
"type": "audio",
"value": "welcome1.mp3"
}],
"channel": "BC"
}
}
What i understand that this can be done before decoding config as well as during decoding config.
The idea here is i want to handle only one of files (either new or old) at my case class level.
Method 1: Tried at config decoding level which works well.
case class File(`type`: String, value: String)
case class Config(files: List[File],
channel: String = "BC")
object Config{
implicit final val FileDecoder: Decoder[File] = deriveDecoder[File]
implicit val ConfigDecoder: Decoder[Config] = (h:HCursor) =>
for {
oldFiles <- h.get[List[File]]("oldFiles")
files <- if (oldFiles.isEmpty) h.get[List[File]]("newFiles") else h.get[List[File]]("oldFiles")
channel <- h.downField("channel").as[String]
}yield{
Config(files, channel)
}
}
case class Inventory(config: Config)
object Inventory {
implicit val InventoryDecoder: Decoder[Inventory] = deriveDecoder[Inventory]
}
Method 2: Tried before feeding into decoding which didn't worked out
Let me know what could be the elegant approach to handle it.
PS: I have multiple similar config decoders and if i handle this at config decoding level then there will be a lot of boiler plate code.
I have simplified the problem a little bit again, but combining this with the previous answer should be simple.
I also took advantage of cats.data.NonEmptyList
final case class Config(files: NonEmptyList[String], channel: String = "BC")
object Config {
implicit final val ConfigDecoder: Decoder[Config] =
(
(Decoder[NonEmptyList[String]].at(field = "newFiles") or Decoder[NonEmptyList[String]].at(field = "oldFiles")),
Decoder[String].at(field = "channel")
).mapN(Config.apply).at(field = "config")
}
This can be used like this:
val data =
"""[
{
"config": {
"newFiles": ["Foo", "Bar", "Baz"],
"oldFiles": [],
"channel": "BC"
}
},
{
"config": {
"newFiles": [],
"oldFiles": ["Quax"],
"channel": "BC"
}
}
]"""
parser.decode[List[Config]](data)
// res: Either[io.circe.Error, List[Config]] =
// Right(List(
// Config(NonEmptyList("Foo", "Bar", "Baz"), "BC"),
// Config(NonEmptyList("Quax"), "BC")
// ))
Note: I am assuming that at least one of the two lists must be non-empty and give priority to the new one.
You can see the code running here.

How to serialize fields with varying type?

I have the following data classes to parse JSON. I can parse it easily with the decodeFromString method. However, the Info classes could contain the List<Int> type from time to time along with the Int type so that both are included in a single JSON. How can I handle this variation in serialization?
#Serializable
data class Node (#SerialName("nodeContent") val nodeContent: List<Info>)
#Serializable
data class Info (#SerialName("info") val info: Int)
p.s. The closest question to mine is this one: Kotlinx Serialization, avoid crashes on other datatype. I wonder if there are other ways?
EDIT:
An example is given below.
"nodeContent": [
{
"info": {
"name": "1",
},
},
{
"info": [
{
"name": "1"
},
{
"name": "2"
},
],
},
{
"info": {
"name": "2",
},
}
]
Here is an approach with a custom serializer similar to the link you provided. The idea is to return a list with just a single element.
// Can delete these two lines, they are only for Kotlin scripts
#file:DependsOn("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0")
#file:CompilerOptions("-Xplugin=/snap/kotlin/current/lib/kotlinx-serialization-compiler-plugin.jar")
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.encoding.Decoder
#Serializable
data class Node (val nodeContent: List<Info>)
#Serializable(with = InfoSerializer::class)
data class Info (val info: List<Name>)
#Serializable
data class Name (val name: Int)
#Serializer(forClass = Info::class)
object InfoSerializer : KSerializer<Info> {
override fun deserialize(decoder: Decoder): Info {
val json = ((decoder as JsonDecoder).decodeJsonElement() as JsonObject)
return Info(parseInfo(json))
}
private fun parseInfo(json: JsonObject): List<Name> {
val info = json["info"] ?: return emptyList()
return try {
listOf(Json.decodeFromString<Name>(info.toString()))
} catch (e: Exception) {
(info as JsonArray).map { Json.decodeFromString<Name>(it.toString()) }
}
}
}
Usage:
val ss2 = """
{
"nodeContent": [
{
"info":
{"name": 1}
},
{
"info": [
{"name": 1},
{"name": 2}
]
},
{
"info":
{"name": 2}
}
]
}
"""
val h = Json.decodeFromString<Node>(ss2)
println(h)
Result:
Node(nodeContent=[Info(info=[Name(name=1)]), Info(info=[Name(name=1), Name(name=2)]), Info(info=[Name(name=2)])])

How to traverse list of nested maps in scala

I have been given a json string that looks like the following one:
{
"dataflows": [
{
"name": "test",
"sources": [
{
"name": "person_inputs",
"path": "/data/input/events/person/*",
"format": "JSON"
}
],
"transformations": [
{
"name": "validation",
"type": "validate_fields",
"params": {
"input": "person_inputs",
"validations": [
{
"field": "office",
"validations": [
"notEmpty"
]
},
{
"field": "age",
"validations": [
"notNull"
]
}
]
}
},
{
"name": "ok_with_date",
"type": "add_fields",
"params": {
"input": "validation_ok",
"addFields": [
{
"name": "dt",
"function": "current_timestamp"
}
]
}
}
],
"sinks": [
{
"input": "ok_with_date",
"name": "raw-ok",
"paths": [
"/data/output/events/person"
],
"format": "JSON",
"saveMode": "OVERWRITE"
},
{
"input": "validation_ko",
"name": "raw-ko",
"paths": [
"/data/output/discards/person"
],
"format": "JSON",
"saveMode": "OVERWRITE"
}
And I have been asked to use it as some kind of recipe for an ETL pipeline, i.e., the data must be extracted from the "path" specifid in the "sources" key, the transformations to be carried out are specified within the "transformations" key and, finally, the transformed data must saved to one of the two specified "sink" keys.
I have decided to convert the json string into a scala map, as follows:
val json = Source.fromFile("path/to/json")
//parse
val parsedJson = jsonStrToMap(json.mkString)
implicit val formats = org.json4s.DefaultFormats
val parsedJson = parse(jsonStr).extract[Map[String, Any]]
so, with that, I get a structure like this one:
which is a map whose first value is a list of maps. I can evaluate parsedJson("dataflows") to get:
which is a list, as expected, but, then I cannot traverse such list, even though I need to in order to get to the sources, transformations and sinks. I have tried using the index of the listto, for example, get its first element, like this: parsedJson("dataflows")(0), but to no avail.
Can anyone please help me traverse this structure? Any help would be much appreciated.
Cheers,
When you evaluate parsedJson("dataflows") a Tuple2 is returned aka a Tuple which has two elements that are accessed with ._1 and ._2
So for dataflows(1)._1 the value returned is "sources" and dataflows(1)._2 is list of maps (List[Map[K,V]) which can be traversed like you would normally traverse elements of a List where each element is Map
Let's deconstruct this for example:
val dataFlowsZero = ("sources", List(Map(42 -> "foo"), Map(42 -> "bar")))
The first element in the Tuple
scala> dataFlowsZero._1
String = sources
The second element in the Tuple
scala> dataFlowsZero._2
List[Map[Int, String]] = List(Map(42 -> foo), Map(42 -> bar))`
Map the keys in each Map in List to a new List
scala> dataFlowsZero._2.map(m => m.keys)
List[Iterable[Int]] = List(Set(42), Set(42))
Map the values in each Map in the List to a new List
scala> dataFlowsZero._2.map(m => m.values)
List[Iterable[String]] = List(Iterable(foo), Iterable(bar))
The best solution is to convert the JSON to the full data structure that you have been provided rather than just Map[String, Any]. This makes it trivial to pick out the data that you want. For example,
val dataFlows = parse(jsonStr).extract[DataFlows]
case class DataFlows(dataflows: List[DataFlow])
case class DataFlow(name: String, sources: List[Source], transformations: List[Transformation], sinks: List[Sink])
case class Source(name: String, path: String, format: String)
case class Transformation(name: String, `type`: String, params: List[Param])
case class Param(input: String, validations: List[Validation])
case class Validation(field: String, validations: List[String])
case class Sink(input: String, name: String, paths: List[String], format: String, saveMode: String)
The idea is to make the JSON handler do most of the work to create a type-safe version of the original data.

Swift : How to create a dictionary dynamically from a json?

I'd like some advice from you. I would like to create a dictionary from a dynamic response fetch from an API and send that dictionary in an Alamofire POST request.
From what I have so far it's working but I'm not satisfied with what i've made and I think the code is really messy.
Here is an example of what I can receive
"content": {
"type": "form",
"fields": [
{
"type": "select",
"label": "Do you have your documents?",
"field": "user.has_docs",
"default": 0,
"choices": [
{
"value": 0,
"name": "Not yet"
},
{
"value": 1,
"name": "I do"
}
]
},
{
"type": "input",
"field": "user.date",
"label": "When do you arrive?",
}
]
}
After parsing the json with the Codable protocol, I have all my data in the Model Field
type: String
label: String
field: String
defaultValue: Int?
choice: [Choice]?
Choice
value: Int
name: String
So I want to create my dictionary and I want the following scheme :
{
"value": {
"user": {
"has_docs": 1,
"date": "29/07/2020"
}
}
}
The key named : "value" is always the same value, but the other one depends of the result from the API. the prefix of the field corresponding of "parent object" and the right part is the child.
Hard coding a dictionary in Swift is not that hard, I would do
let dict = [
"value": [
"user": [
"has_docs": 1,
"date": "29/07/2020"
]
]
]
But the troubles begin, at the attempt of creating a dictionary dynamically. Values inside user keep only the last one and replacing has_docs with date.
I have found a workaround with using flatmap and reduce but it only allows the type [String: String], unfortunately I need to write [String: Int] too in the dictionary.
here is a sample of the code
let flattenedDictionary = [key : dictionaries
.flatMap { $0 }
.reduce([String:String]()) { (dict, tuple) in
var nextDict = dict
nextDict.updateValue(tuple.1 as! String, forKey: tuple.0)
return nextDict
}]
parameters["value"] = flattenedDictionary
Here :
key = "user".
dictionaries = [["has_docs": 1], ["date": "29/07/2020"]]
Feel free to exchange if you need more informations
If you have any clue on how you could helping me, I'll highly appreciate, thanks for reading so far.
I hope I was very understandable.
Edit
From a general view : I'd like to create a dictionary dynamically
[String: [String: [String: Any]]]
A bit unclear if you have a [String: [String: [String: Any]]] or [String: [String: Any]] dictionary, but the concept of creating it dynamically would be rather similar.
var user: [String: Any] = [:]
user["has_docs"] = 1
user["date"] = Date()
let dict = ["value": user]

How to persist an array using Realm Swift and ObjectMapper?

I get an error when I try to save an array that comes from a JSON string. I've tried to use RLMArray with no success.
The error I receive is:
'RLMException', reason: 'Property 'page' is of type 'RLMArray<(null)>' which is not a supported RLMArray object type.
My model class:
public class Project: Object, Mappable {
dynamic var id = 0
dynamic var user: User!
dynamic var page: RLMArray!
dynamic var error_message: String! = ""
dynamic var status: String! = ""
override public static func primaryKey() -> String? {
return "id"
}
required convenience public init?(_ map: Map) {
self.init()
mapping(map)
}
public func mapping(map: Map) {
user <- map["user"]
page <- map["page"]
error_message <- map["error_message"]
status <- map["status"]
}
}
JSON File:
let parameters = [
"user": [
"username": "Marcus",
"password": "123asd"
],
"page": [
"home": [
[
"kind": "navigation",
"title": "suite",
"image": "ic_suite",
"backgroundImage": "ic_background1"
],
[
"kind": "navigation",
"title": "jardim",
"image": "ic_jardim",
"backgroundImage": "ic_background2"
]
],
"suite": [
[
"kind": "button",
"title": "My Master Suite",
"textColor": "0x000000",
"textSize": "16"
]
]
],
"status": "success",
"error_message": ""
]
self.project = Mapper<Project>().map(parameters)
Your class inherits from Object, Realm Swift's base class, but is attempting to use RLMArray, a Realm Objective-C type, in its interface. You cannot mix Realm Swift and Realm Objective-C in this manner. You should use List<T> for array properties when using Realm Swift.