Alk
Alk

Reputation: 5557

Parsing Firebase JSON F#

I'm trying to convert the following Firebase JSON into something that can be parsed in F#:

{  
   "listings":{  
      "-L0pJmU9yj4hAocHjnrB":{  
         "listing_id":"-L0pJmU9yj4hAocHjnrB",
         "location":"Edinburgh",
         "messages":{  
            "SWs56OIGzMdiCjSXahzDQX8zve92":{  
               "-L3ELSSzZPRdjCRcFTrb":{  
                  "senderId":"SWs56OIGzMdiCjSXahzDQX8zve92",
                  "senderName":"alberto",
                  "text":"Hi"
               },
               "-L3EN1NW5hHWBTEGC9ve":{  
                  "senderId":"YMM45tgFFvYB7rx9PhC2TE5eW6D2",
                  "senderName":"David",
                  "text":"Hey"
               }
            }
         }
      },
      "-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"
               },
               "-L3ELGAbcOjdJsHRtnAe":{  
                  "senderId":"YMM45tgFFvYB7rx9PhC2TE5eW6D2",
                  "senderName":"David",
                  "text":"Icvjv"
               }
            }
         }
      },
      "-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"
      }
   }
}

The problem here is that the entries like L0pJmU9yj4hAocHjnrB in the 2nd line and subsequent similar entries are autogenerated timestamp IDs created in Firebase, and they do not have a corresponding name, like for example: "listing_id":"-L0pJmU9yj4hAocHjnrB", therefore I do not know how to set up my F# Records to correctly parse this JSON.

My attempt can be seen below:

type MessageContent = 
    { senderId: string
      senderName: string
      text: string; }

type Message =
     { timestampId : string
       chatMessages : MessageContent;}

type Chat = 
    { chatPartnerId : string 
      Messages : Message array option;}

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 option
      postcode : string
      price_per_night : int
      to_date : string;
    }

type Listing =
     { timestampId : string
       listingcontent : ListingContent option;}

type City = 
    { city : string
      listings : Listing array option
    }

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 option;
    }

type ListingUser = 
    {timestampId : string
     listingUser : ListingUserContent;}

type UserContent = 
    { declined: Declined option
      matches : Match option
      search_settings : SearchSettings option
      listings : ListingUser option;
    }

I use the following code for parsing:

    let myCallbackGetChats (reader:IO.StreamReader) url = 
    let html = reader.ReadToEnd()
    let reader = new JsonTextReader(reader);
    let serializer = JsonSerializer.Create(JsonSerializerSettings(Converters = [| Types.OptionConverter() |]))
    use stringReader = new StringReader(html)
    use jsonReader = new JsonTextReader(stringReader)
    let listings_json = serializer.Deserialize<Types.UserContent>(jsonReader)
    printfn "%A" listings_json

This produces the following output:

    {declined = null;
 matches = null;
 search_settings = null;
 listings = Some {listing_id = null;
                  location = null;
                  messages = null;};}

As we can see the first listings tag is properly deserialized, however as soon as it sees L0pJmU9yj4hAocHjnrB it doesn't know what that is and the rest of the parsing fails. How can I fix this issue?

Upvotes: 0

Views: 100

Answers (1)

dbc
dbc

Reputation: 117076

You can deserialize your JSON with the following F# types:

type MessageContent = 
    { senderId: string
      senderName: string
      text: string; 
    }

type ListingContent = 
    { listing_id : string
      location : string
      messages : Map<string, Map<string, MessageContent>>
      // Add other members as required
    }

type UserContent =
    { listings: Map<string, ListingContent>
      // Add other members as required
    }

And the following line of code:

let listings_json = JsonConvert.DeserializeObject<UserContent>(inputJson)

Notes:

  • The "listing" object consists of variable property names with a fixed schema for their values. As explained in Serialization Guide: Dictionaries and Hashtables such objects can be mapped to a .Net dictionary. In this case I chose the F# Collections.Map<'Key,'Value> class, specifically a Map<string, ListingContent>.

  • Similarly, the "messages" object consists of variable property names whose values are a nested level of objects with variable property names, and so can be represented with a Map<string, Map<string, MessageContent>>.

  • I simplified the UserContent and ListingContent types by removing members not actually present in the sample JSON. You can add them back as required.

  • Since Json.NET has built-in support for dictionaries, a custom JsonConverter is not required for this solution.

Example working F# fiddle.

Upvotes: 2

Related Questions