How do I deserialize JSON with optional properties? - json

I'm trying to deserialize the following JSON:
{
"listings": {
"-L19C5OjcDSjMi4-oha-": {
"listing_id": "-L19C5OjcDSjMi4-oha-",
"location": "Edinburgh"
},
"-L19CJrzEpChO_W14YkC": {
"listing_id": "-L19CJrzEpChO_W14YkC",
"location": "Edinburgh",
"messages": {
"Rp7ytJdEvZeMFgpLqeCSzkSeTyf1": {
"-L19V4QpPMCMwGcNaQBG": {
"senderId": "Rp7ytJdEvZeMFgpLqeCSzkSeTyf1",
"senderName": "Albert",
"text": "Hey there"
},
"-L19r0osoet4f9SjBGE7": {
"senderId": "YMM45tgFFvYB7rx9PhC2TE5eW6D2",
"senderName": "David",
"text": "Hi"
}
}
}
},
"-L19ChjPjX1DnfQb28AW": {
"listing_id": "-L19ChjPjX1DnfQb28AW",
"location": "Edinburgh",
"messages": {
"879dUqGuiXSd95QHzfhbSs05IZn2": {
"-L1i6c7sGf3BcF2cCSCu": {
"senderId": "879dUqGuiXSd95QHzfhbSs05IZn2",
"senderName": "Alberto",
"text": "Hello"
}
},
"Rp7ytJdEvZeMFgpLqeCSzkSeTyf1": {
"-L19FGCMuQACjYKCFEwV": {
"senderId": "Rp7ytJdEvZeMFgpLqeCSzkSeTyf1",
"senderName": "Albert",
"text": "Hey"
},
"-L19T_v2Utxhu1mGhz7-": {
"senderId": "YMM45tgFFvYB7rx9PhC2TE5eW6D2",
"senderName": "David",
"text": "Hi"
},
"-L19TbhActGmga4f47Mz": {
"senderId": "Rp7ytJdEvZeMFgpLqeCSzkSeTyf1",
"senderName": "Albert",
"text": "How are you"
}
}
}
},
"-L19Cz1abm1o-JCbiAnN": {
"listing_id": "-L19Cz1abm1o-JCbiAnN",
"location": "Edinburgh"
},
"-L19DMdFx2pXj9-EKCq2": {
"listing_id": "-L19DMdFx2pXj9-EKCq2",
"location": "Edinburgh"
},
"-L19DV67WjguozFE_4dM": {
"listing_id": "-L19DV67WjguozFE_4dM",
"location": "Edinburgh"
}
}
}
In order to do so I have created the following records:
type MessageContent =
{ senderId: string
senderName: string
text: string; }
type Message =
{ timestampId : string
chatMessages : MessageContent;}
type Chat =
{ chatPartnerId : string
Messages : Message array;}
type ListingContent =
{ from : string
landlord_id : string
listing_id : string
location : string
name : string
pic_1_url : string
pic_2_url : string
pic_3_url : string
pic_4_url : string
pic_5_url : string
messages : Chat array
postcode : string
price_per_night : int
to_date : string;
}
type Listing =
{ timestampId : string
chatMessages : ListingContent;}
type City =
{ city : string
listings : Listing array
}
type AllListings =
{ cities : City array;}
type SearchSettings =
{ from : string
location : string
max_price : decimal
min_price : decimal
to_date : string;}
type MatchContent =
{ id : string
location : string;}
type Match =
{timestampId : string
matchContent : MatchContent;}
type DeclinedContent =
{ id : string;
}
type Declined =
{timestampId : string
declinedContent : DeclinedContent;}
type ListingUserContent =
{ listing_id : string
location : string
messages : Chat array;
}
type ListingUser =
{timestampId : string
listingUser : ListingUserContent;}
type UserContent =
{ declined: Declined array
matches : Match array
searchSettings : SearchSettings
user_listings : ListingUser array;
}
Next, I have the following line of code:
let listings = JsonConvert.DeserializeObject<Types.UserContent>(html)
where html is the JSON string shown above.
However, this throws the following error:
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Types+Declined[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'declined.-L0tmKVgUcj_a1ubO5Zd', line 1, position 36.
I believe this might because there is no Declined in this particular JSON, however all of the 4 members of the UserContent Record are completely optional (they might all be there, or none of them might be there)...is this what I'm doing wrong? If so, how do I fix it, and allow for optional values.
UPDATE:
So I commented out the code which actually does the deserialization and I'm still getting the weird error, I don't think its related to my code

Make the members of your UserContent record Option types, and add a TypeConverter for F# Option types, such as this one, to your JSON Serialization Settings.
type UserContent =
{ declined: Declined array option
matches : Match array option
searchSettings : SearchSettings option
user_listings : ListingUser array option;
}
type OptionConverter() =
inherit JsonConverter()
override __.CanConvert(t) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>>
override __.WriteJson(writer, value, serializer) =
let value =
if value |> isNull
then null
else let _,fields = FSharpValue.GetUnionFields(value, value.GetType())
fields.[0]
serializer.Serialize(writer, value)
override __.ReadJson(reader, t, existingValue, serializer) =
let innerType = t.GetGenericArguments().[0]
let innerType =
if innerType.IsValueType
then (typedefof<Nullable<_>>).MakeGenericType([|innerType|])
else innerType
let value = serializer.Deserialize(reader, innerType)
let cases = FSharpType.GetUnionCases(t)
if value |> isNull
then FSharpValue.MakeUnion(cases.[0], [||])
else FSharpValue.MakeUnion(cases.[1], [|value|])
let serializer = JsonSerializer.Create(JsonSerializerSettings(Converters = [| OptionConverter() |]))
use stringReader = new StringReader(html)
use jsonReader = new JsonTextReader(stringReader)
serializer.Deserialize<Types.UserContent>(jsonReader)

Related

parse generic sealed class in kotlin using jackson

I have the following generic sealed class representing the status of network response.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "status")
#JsonSubTypes(
value = [
JsonSubTypes.Type(value = Response.OK::class, name = "OK"),
JsonSubTypes.Type(value = Response.Error::class, name = "ERROR"),
]
)
sealed class Response<out Content, out Error> {
data class OK<out Content>(val content: Content) : Response<Content, Nothing>()
data class Error<out Error>(val error: Error) : Response<Nothing, Error>()
}
The network response json can have status": "OK" in which case it contains a content key or a "status": "ERROR" in which case it contains a error key.
The type under the content and error key can be different for each endpoint I’m talking to. Hence the need for generic types
So for example one endpoit returns String as types, so Response<String, String>
{
"status": "OK",
"content": "Hello"
}
{
"status": "ERROR",
"error": "MyError"
}
Another endpoit return Double as content and Int as error, so
Response<Double, Int>
{
"status": "OK",
"content": 2.0
}
{
"status": "ERROR",
"error": 1
}
My parsing fails though with message
Could not resolve type id 'OK' as a subtype of `com.example.models.Response<java.lang.String,java.lang.String>`: Failed to specialize base type com.example.models.Response<java.lang.String,java.lang.String> as com.example.models.Response$OK, problem: Type parameter #1/2 differs; can not specialize java.lang.String with java.lang.Object
at [Source: (String)"{
"status": "OK",
"content": "Hello"
}"; line: 2, column: 13]
#Nested
inner class ContentParsingTests {
#Test
fun `parses OK response`() {
val json = """
{
"status": "OK",
"content": "Hello"
}
""".trimIndent()
when (val result = objectMapper.readValue<Response<String, String>>(json)) {
is Response.OK -> {
assertEquals(result.content, "Hello")
}
is Response.Error -> {
fail()
}
}
}
#Test
fun `parses ERROR response`() {
val json = """
{
"status": "ERROR",
"error": "MyError"
}
""".trimIndent()
when (val result = objectMapper.readValue<Response<String, String>>(json)) {
is Response.OK -> {
fail()
}
is Response.Error -> {
assertEquals(result.error, "MyError")
}
}
}
}
I noticed that the parsing works fine if only the content is generic:
sealed class Response<out Content > {
data class OK<out Content>(val content: Content) : Response<Content>()
object Error : Response<Nothing>()
}
but of course I loose the error payload
What would be a correct way to parse the json into my generic class?
I think the issue is with Nothing because it's like Void and you can't create an instance of it or get a type information that's why the serialization library struggling with it. so a solution for the current problem is to update the model definition like this and it works. It's not ideal though.
sealed class Response<out Content, out Error> {
data class OK<out Content, out Error>(val content: Content) : Response<Content, Error>()
data class Error<out Content, out Error>(val error: Error) : Response<Content, Error>()
}
You don't need generics at all for this case. Just have:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "status")
#JsonSubTypes(
value = [
JsonSubTypes.Type(value = Response.OK::class, name = "OK"),
JsonSubTypes.Type(value = Response.Error::class, name = "ERROR"),
]
)
sealed interface Response {
data class Success(val content: Content): Response
data class Error(val error: Error): Response
}
Jackson will then be able to parse everything correctly.

How to use Gson to deserialize a json array string to object mode using generic class type?

Having a function for deserializing json string into object mode by providing the class type the json string should be deserialized into.
It works for most of the case
fun <T> deserializeJsonStr(jsonString: String, dataClassType: Class<T>):T? {
var ret: T? = Gson().fromJson<T>(jsonString, dataClassType)
return ret
}
like
// data modle
class TestData1 {
var userId = 0
var id = 0
var title: String? = null
var body: String? = null
}
// the json string
{
"userId": 1,
"id": 3,
"title": "the title",
"body": "the body"
}
// call the function
val clazz: Class<TestData1> = TestData1::class.java
val theTestData1 = deserializeJsonStr(jsonString, clazz)
it will return the object theTestData1 with the fields filled.
But if the json string is for json array:
[
{
"userId": 1,
"id": 1,
"title": "title1",
"body": "body1"
},
{
"userId": 1,
"id": 2,
"title": "title2",
"body": "body2"
},
{
"userId": 1,
"id": 3,
"title": "title3",
"body": "body3"
}
]
Though it should be ArrayList<TestData1>, but what is it class type? tried:
val clazz: Class<ArrayList<TestData1>> = ArrayList<TestData1>::class.java
val theTestData1 = psreJsonStr(jsonString, clazz)
but it does not compile:
or the val pojoClass: Class<ArrayList<TestData1>> = ArrayList<>::class.java does not compile either:
what would be the data class mode for this json array? or what is the Class<T> required by the function param for ArrayList?
This is related to type erasure: ArrayList<TestData1>::class.java is not possible because at runtime the type argument of ArrayList is not available, so this would basically be Class<ArrayList>1. Therefore the compiler does not allow this because it would not be safe.
Gson has its TypeToken class to work around this, see also the user guide.
In your case you could change your deserializeJsonStr to the following:
fun <T> deserializeJsonStr(jsonString: String, dataType: TypeToken<T>): T? {
// Note: Older Gson versions might require using `dataType.type`
return Gson().fromJson(jsonString, dataType)
}
You would then call the function like this:
val type = object: TypeToken<ArrayList<TestData1>>() {}
val data = deserializeJsonStr(jsonString, type)
You could additionally add an overload of deserializeJsonStr with reified type parameter which makes it make more convenient to use:
inline fun <reified T> deserializeJsonStr(jsonString: String): T? {
return deserializeJsonStr(jsonString, object: TypeToken<T>() {})
}
// Note: You can even omit the explicit `ArrayList<TestData1>` if the compiler can infer it
val data = deserializeJsonStr<ArrayList<TestData1>>(jsonString, type)
1 Class is a bit special because its direct type argument is not erased at runtime, for example Class<ArrayList> is not erased to Class<Object>. But for every other generic type the type argument is erased.

Create a JSON document from NSObject with SwiftyJSON

I'm trying to create a JSON array of objects, to manipulate and save in UserPreferences after, like this:
[
{
"id" : "01",
"title" : "Title"
},
{
"id" : "02",
"title": "Title 02"
}
]
this is my NSObject class:
class Item: NSObject {
var _id: String = ""
var _title: String = ""
var id: String {
get {
return _id
}
set {
_id = newValue
}
}
var title: String {
get {
return _title
}
set {
_title = newValue
}
}
}
And I have this code to convert to JSON using SwiftyJson, but I cant make this like a array
var item: [Item] = ["array of itens already setted"]
var json: JSON = JSON([:])
for item in list {
json["id"].string = item.id
json["title"].string = item.title
}
This code return just the last item of array:
{
"id" : "01",
"title" : "Title"
}
Problem lies here , as loop iterates you are setting values in same object.
var item: [Item] = ["array of itens already setted"]
var json: [JSON] = [JSON([:])]. -----> this should be array not just object
for item in list {
json["id"].string = item.id
json["title"].string = item.title
}
instead use this :
var item: [Item] = ["array of itens already setted"]
var json: [JSON] = [JSON([:])]. -----> json array
for item in list {
let jsonTemp: JSON = JSON([:])
jsonTemp["id"].string = item.id
jsonTemp["title"].string = item.title
json.append(jsonTemp)
}
print("[JSON OBJECT Count :: \(json.count), Informations are : \(json)]")
I'd suggest that class Item adopts Codable protocol.
Then have JSONEncoder do the job. That way, you can even nest the resulting JSON inside more complex type.
Also check this out for how to customize key names.
let items = [Item(), Item()]
items[0].id = "01"
items[0].title = "Title"
items[1].id = "02"
items[1].title = "Title 02"
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
print(String(data: try encoder.encode(items), encoding: .ascii)!)
/* result
[
{
"_id" : "01",
"_title" : "Title"
},
{
"_id" : "02",
"_title" : "Title 02"
}
]
*/

Decode JSON Multiway Tree into an F# Multiway Tree Discriminated Union

I have the following JSON data in a documentdb and I would like to parse this into an F# multiway tree discriminated union
"commentTree": {
"commentModel": {
"commentId": "",
"userId": "",
"message": ""
},
"forest": []
}
F# multiway discriminated union
type public CommentMultiTreeDatabaseModel =
| CommentDatabaseModelNode of CommentDatabaseModel * list<CommentMultiTreeDatabaseModel>
where CommentMultiTreeDatabaseModel is defined as
type public CommentDatabaseModel =
{ commentId : string
userId : string
message : string
}
I am referencing Fold / Recursion over Multiway Tree in f# extensively. I am not sure where to begin to parse such a JSON structure into an F# multiway tree. Any suggestions will be much appreciated. Thanks
One way to think about this is by looking at what data you need in order to construct a CommentMultiTreeDatabaseModel. It needs a CommentDatabaseModel and a list of CommentMultiTreeDatabaseModel. So we need to write the following two functions:
let parseComment (input : JSON) : CommentDatabaseModel =
...
let parseTree (input : JSON) : CommentMultiTreeDatabaseModel =
...
But wait, the parseTree function is the one we're trying to write right now! So instead of writing a new function, we just mark our current function with the rec keyword and have it call itself where needed.
Below is a rough example of how it could be done. The key thing to look at is parseTree which builds up the data by recursively calling itself. I've represented the JSON input data with a simple DU. A library like Chiron can produce something like this.
Note that this code parses all of the JSON in one go. Also, it's not tail-recursive, so you'll have to be careful with how deep your tree structure is.
[<RequireQualifiedAccess>]
type JSON =
| String of string
| Object of (string * JSON) list
| Array of JSON list
type public CommentDatabaseModel = {
commentId : string
userId : string
message : string
}
type public CommentMultiTreeDatabaseModel =
| CommentDatabaseModelNode of CommentDatabaseModel * list<CommentMultiTreeDatabaseModel>
let parseComment = function
| JSON.Object [ "commentId", JSON.String commentId; "userId", JSON.String userId; "message", JSON.String message ] ->
{
commentId = commentId
userId = userId
message = message
}
| _ -> failwith "Bad data"
let rec parseTree (input : JSON) : CommentMultiTreeDatabaseModel =
match input with
| JSON.Object [ "commentModel", commentModel; "forest", JSON.Array forest ] ->
CommentDatabaseModelNode (parseComment commentModel, List.map parseTree forest)
| _ -> failwith "Bad data"
let parse (input : JSON) : CommentMultiTreeDatabaseModel =
match input with
| JSON.Object [ "commentTree", commentTree ] ->
parseTree commentTree
| _ -> failwith "Bad data"
let comment text =
JSON.Object [
"commentId", JSON.String ""
"userId", JSON.String ""
"message", JSON.String text
]
let sampleData =
JSON.Object [
"commentTree", JSON.Object [
"commentModel", comment "one"
"forest", JSON.Array [
JSON.Object [
"commentModel", comment "two"
"forest", JSON.Array []
]
JSON.Object [
"commentModel", comment "three"
"forest", JSON.Array []
]
]
]
]
parse sampleData
(*
val it : CommentMultiTreeDatabaseModel =
CommentDatabaseModelNode
({commentId = "";
userId = "";
message = "one";},
[CommentDatabaseModelNode ({commentId = "";
userId = "";
message = "two";},[]);
CommentDatabaseModelNode ({commentId = "";
userId = "";
message = "three";},[])])
*)

Realm + Swift, nested JSON

I have a problem since last 2 days. I can't get my JSON transformed to a Realm Object.
I have a json like below:
{
"gender" : "male",
"id" : "123456789",
"age_range" : {
"min" : 21
},
"last_name" : "LastName"
}
I have this Realm Models:
class UserObject: Object {
dynamic var userId: String = ""
dynamic var lastName: String?
dynamic var gender: String?
var ageRange = List<AgeRangeObject>()
required convenience init?(_ map: Map) {
self.init()
}
}
class AgeRangeObject: Object {
dynamic var min: Int = 0
}
And the way I am trying to create an instance of this model with ObjectMapper to parse json to dictionary and then create the model instance:
let userJSONModel = Mapper<User>().map(jsonString)
let realm = try! Realm()
do {
try realm.write {
let dict: [String : AnyObject] = [
"userId" : (userJSONModel?.userId)!,
"ageRange" : (userJSONModel?.ageRange)!,
"lastName" : (userJSONModel?.lastName)!,
"gender" : (userJSONModel?.gender)!
]
let userModel = UserObject(value: dict)
realm.add(userModel)
}
} catch {
print("Exception")
}
The problem occurs on this line: let userModel = UserObject(value: dict)
I get the folowing error:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Invalid value 'min' to initialize object of type 'AgeRangeObject': missing key 'min''
I was looking on the stackoverflow:
Nested Arrays throwing error in realm.create(value: JSON) for Swift
How to convert Realm object to JSON with nested NSDate properties?
but my case is different.
Do you know what's the problem with that age range dictionary? Why it can't parse it well?
Thank you.
In your JSON, ageRange is a dictionary, whereas the UserObject.ageRange property is a List<AgeRangeObject>. You have mismatched models.
You either need to update your models to reflect the structure of your JSON:
var ageRange = List<AgeRangeObject>()
becomes
dynamic var ageRange: AgeRangeObject? = nil
or vice versa, update your JSON to reflect the structure of your models:
{
"gender" : "male",
"id" : "123456789",
"age_range" : [{
"min" : 21
}],
"last_name" : "LastName"
}
{
"key1" : "value1",
"key2" : "value2",
"array1" : [{
"key" : value
}],
"key3" : "value3"
}
For this you could use ObjectMapper's TransformType.
Reference: https://github.com/APUtils/ObjectMapperAdditions
My Code:
#objcMembers class RealmObject: Object, Mappable {
dynamic var listValues = List<MyRealmObject>()
required convenience init?(map: Map) {
self.init()
}
// Mappable
func mapping(map: Map) {
listValues <- (map["listValues"], RealmlistObjectTransform())
}
}
#objcMembers class MyRealmObject: Object, Mappable {
required convenience init?(map: Map) {
self.init()
}
// Mappable
func mapping(map: Map) {
}
}
class RealmlistObjectTransform: TransformType {
typealias Object = List<MyRealmObject> // My Realm Object here
typealias JSON = [[String: Any]] // Dictionary here
func transformFromJSON(_ value: Any?) -> List<MyRealmObject>? {
let list = List<MyRealmObject>()
if let actors = value as? [[String: Any]] {
let objects = Array<MyRealmObject>(JSONArray: actors)
list.append(objectsIn: objects)
}
return list
}
func transformToJSON(_ value: List<MyRealmObject>?) -> [[String: Any]]? {
if let actors = value?.sorted(byKeyPath: "").toArray(ofType: MyRealmObject.self).toJSON() {
return actors
}
return nil
}
}