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

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";},[])])
*)

Related

Modify json values from list of maps and save output

I have the following example json that I read in using the play framework.
{
"field_a": "dummy",
"field_b": "dummy",
"nest": {
"nest_a": "dummy",
"nest_b": 87
},
"field_c": null,
"field_d": null,
"field_e": "chocolate",
"field_f": "sugar",
"array": [
"dummy entry"
],
"id": "Anything"
}
I Then have the following List of Maps that I want to swap out data with which is my Input:
val substitutionsList: List[mutable.Map[String, String]] = List(
mutable.Map("field_b" -> "dummy string", "field_d" -> "2016-01-01", "field_f" -> "2011-01-01"),
mutable.Map("field_b" -> "dummy string", "field_d" -> "2018-01-01", "field_f" -> "2018-01-01"),
mutable.Map("field_b" -> "dummy string", "field_d" -> "2018-04-01", "field_f" -> "2018-04-01"),
mutable.Map("field_b" -> "dummy string", "field_d" -> "2016-01-01", "field_f" -> "2016-01-01")
)
I am reading in the json as follows:
def parseSchemaJson(schemaContent: String) = Json.parse(schemaContent).as[JsObject]
val baseSchemaInput = parseSchemaJson(Source.fromFile("/dummy.json").mkString)
I want to iterate over my Input and swap out the values in the json for the values in my map and after each one is done, create a new .json file.
private def replaceField(json: JsObject, fieldToReplace: String): Option[String] = (json \ fieldToReplace).asOpt[String]
println(replaceField(baseSchemaInput, "field_a")) //prints dummy
I can list out the value in my json using something like this but I have no idea how to swap the value from my list into each respective bit and write out a json file.
First occurance of the expected output
{
"field_a": "dummy",
"field_b": "dummy string",
"nest": {
"nest_a": "dummy",
"nest_b": 87
},
"field_c": null,
"field_d": "2016-01-01",
"field_e": "chocolate",
"field_f": "2011-01-01",
"array": [
"dummy entry"
],
"id": "Anything"
}
Given the following substitution list:
val substitutionsList: List[mutable.Map[String, Any]] = List(
mutable.Map("field_b" -> "dummy string 1", "field_d" -> "2016-01-01", "field_f" -> "2011-01-01"),
mutable.Map("field_b" -> "dummy string 2", "nest" -> mutable.Map("nest_b" -> "90"))
)
Note that the second one updates a nested value. You need to create a nested Map to define nested values.
You can define a function to transform a Map[String, Any] (Any because we can have either a String or a Map as value) into a JsObject. This is a recursive function and will call itself in case the Value is a Map
def mapToJsObject(map: mutable.Map[String, Any]): JsObject =
JsObject(map.mapValues {
case v:String => JsString(v)
case v:mutable.Map[String, mutable.Map[String, Any]] => mapToJsObject(v.asInstanceOf[mutable.Map[String, Any]])
})
Then go through your list of substitutions and deep merge the JsObject made from each Map with the original JsObject using the deepMerge function defined on the JsObject class. It will merge the nested objects too. See API here
val substitutedJsObjects: List[JsObject] = substitutionsList
.map(mapToJsObject)
.map(baseSchemaInput.deepMerge)
This should give you a list of JsObjects, one per Map in your List
You can then write them to file. Here is an example to write one file per json string. Files will be named 0.json, 1.json, etc ..
def writeToFile(jsObject: JsObject, fileName: String): Unit = {
println("writing "+fileName)
val pw = new PrintWriter(new File(fileName))
pw.write(jsObject.toString())
pw.close()
}
substitutedJsObjects.zipWithIndex.foreach {
case (jsObject, index) => {
val fileName = index.toString + ".json"
writeToFile(jsObject, fileName)
}
}

Elm: decoding json from http response and showing it

I'm kind of new to Elm and I find it very hard to decode a json from a http response.
The app I'm making is doing a call to gravatar and receives a profile.
I'd like to extract some fields from the response and put in in a record, which in turn in shown in the view.
This is my code:
-- MODEL
type alias MentorRecord =
{ displayName : String
, aboutMe : String
, currentLocation : String
, thumbnailUrl : String
}
type alias Model =
{ newMentorEmail : String
, newMentor : MentorRecord
, mentors : List MentorRecord
}
init : ( Model, Cmd Msg )
init =
( Model "" (MentorRecord "" "" "" "") [], Cmd.none )
-- UPDATE
type Msg
= MentorEmail String
| AddMentor
| GravatarMentor (Result Http.Error MentorRecord)
| RemoveMentor
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MentorEmail newEmail ->
( { model | newMentorEmail = newEmail }, Cmd.none )
AddMentor ->
( model, getGravatarMentor model.newMentorEmail )
GravatarMentor (Ok addedMentor) ->
( Model "" addedMentor (addedMentor :: model.mentors)
, Cmd.none
)
GravatarMentor (Err _) ->
( model, Cmd.none )
RemoveMentor ->
( model, Cmd.none )
-- VIEW
view : Model -> Html Msg
view model =
div []
[ input [ placeholder "Email adress mentor", onInput MentorEmail ] []
, button [ onClick AddMentor ] [ text "Add Mentor" ]
, br [] []
, img [ src (createIconUrl model.newMentorEmail) ] []
, div [] [ text model.newMentor.displayName ]
, div [] [ toHtmlImgList model.mentors ]
]
toHtmlImgList : List MentorRecord -> Html Msg
toHtmlImgList mentors =
ul [] (List.map toLiImg mentors)
toLiImg : MentorRecord -> Html Msg
toLiImg mentor =
li [] [ img [ src mentor.thumbnailUrl ] [] ]
-- HTTP
getGravatarMentor : String -> Cmd Msg
getGravatarMentor newMentorEmail =
Http.send GravatarMentor
(Http.get (createProfileUrl newMentorEmail) decodeGravatarResponse)
createProfileUrl : String -> String
createProfileUrl email =
"https://en.gravatar.com/" ++ MD5.hex email ++ ".json"
createIconUrl : String -> String
createIconUrl email =
"https://www.gravatar.com/avatar/" ++ MD5.hex email
decodeGravatarResponse : Decoder MentorRecord
decodeGravatarResponse =
let
mentorDecoder =
Json.Decode.Pipeline.decode MentorRecord
|> Json.Decode.Pipeline.required "displayName" string
|> Json.Decode.Pipeline.required "aboutMe" string
|> Json.Decode.Pipeline.required "currentLocation" string
|> Json.Decode.Pipeline.required "thumbnailUrl" string
in
at [ "entry" ] mentorDecoder
If a valid email address if filled in (i.e. one with a gravatar profile), you see the icon. But what this code also should do is extract name, location, about me info, thumbnailUrl from another http response, put it in a list, and show it in the view. And that's not happening if you click on 'Add mentor'
So I guess the decoding part isn't going very well, but I'm not sure (maybe because the nested element is in a list?).
A response from gravatar looks like this (removed some fields in entry):
{ "entry": [
{
"preferredUsername": "bla",
"thumbnailUrl": "https://secure.gravatar.com/avatar/hashinghere",
"displayName": "anne",
"aboutMe": "Something...",
"currentLocation": "Somewhere",
}
]}
Code in Ellie app: https://ellie-app.com/n5dxHhvQPa1/1
entry is an array. To decode the contents of the first element of the array, you need to use Json.Decode.index.
Change:
(at [ "entry" ]) mentorDecoder
to
(at [ "entry" ] << index 0) mentorDecoder
But the bigger problem here is that Gravatar does not support cross origin requests (CORS) but only JSONP. elm-http doesn't support JSONP. You can either use ports for that or use a third party service which enables you to make CORS requests to arbitrary sites. I've used the latter in the ellie link below but you should use ports or your own CORS proxy in a real production application.
I also made aboutMe and currentLocation optional as they weren't present in the profile I checked. Here's the link: https://ellie-app.com/pS2WKpJrFa1/0
The original functions:
createProfileUrl : String -> String
createProfileUrl email =
"https://en.gravatar.com/" ++ MD5.hex email ++ ".json"
decodeGravatarResponse : Decoder MentorRecord
decodeGravatarResponse =
let
mentorDecoder =
Json.Decode.Pipeline.decode MentorRecord
|> Json.Decode.Pipeline.required "displayName" string
|> Json.Decode.Pipeline.required "aboutMe" string
|> Json.Decode.Pipeline.required "currentLocation" string
|> Json.Decode.Pipeline.required "thumbnailUrl" string
in
at [ "entry" ] mentorDecoder
The changed functions:
createProfileUrl : String -> String
createProfileUrl email =
"https://crossorigin.me/https://en.gravatar.com/" ++ MD5.hex email ++ ".json"
decodeGravatarResponse : Decoder MentorRecord
decodeGravatarResponse =
let
mentorDecoder =
Json.Decode.Pipeline.decode MentorRecord
|> Json.Decode.Pipeline.required "displayName" string
|> Json.Decode.Pipeline.optional "aboutMe" string ""
|> Json.Decode.Pipeline.optional "currentLocation" string ""
|> Json.Decode.Pipeline.required "thumbnailUrl" string
in
(at [ "entry" ] << index 0) mentorDecoder

How do I deserialize JSON with optional properties?

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)

How to Parse Json String received from HTTP and loop through the values

I'm using Scala and Swagger and i need help figuring out how to loop through the values in the json and use those values for checking and others.
The json string that is returned after HTTP get request looks like this:
{
"count": 3,
"items": [
{
"Id": "fd0a9e5a",
"DbName": "Xterior Prod",
"Name": "XP"
},
{
"Id": "4158a1a6",
"DbName": "Invidibi Pappear",
"Name": "ISP"
},
{
"Id": "7e0c57046d3f",
"DbName": "Multi Test",
"Name": "MMP"
}]
}
My UI allows the user to input an ID. What i have to do is to loop through the Json value returned from the API and find the one that matches the ID entered. Once i find a match, i have to check if the database has "Test" keyword in it. If it does, i will need to show the DbName and the shortname.
I have found some guide here (e.g. Foreach with JSON Arrays in Play2 and Scala) but it did not work for me. When i run my code, i get this error:
play.api.libs.json.JsResultException: JsResultException(errors:List(((0)/Id,List(ValidationError(List(error.path.missing),WrappedArray()))), ((0)/DbName,List(ValidationError(List(error.path.missing),WrappedArray()))), ((1)/Id,List(ValidationError(List(error.path.missing),WrappedArray()))), ((1)/DbName,List(ValidationError(List(error.path.missing),WrappedArray()))), ((2)/Id,List(ValidationError(List(error.path.missing),WrappedArray()))), ((2)/DbName,List(ValidationError(List(error.path.missing),WrappedArray()))),
Here is my code:
case class DBInfo(Id: String, DbName: String, Name: String)
contentType = "application/json"
//get json from http
val httpClient = HttpClients.createDefault()
val httpResponse = httpClient.execute(new HttpGet("http://www.customers.com/dbInfo"))
val entity = httpResponse.getEntity
val content = fromInputStream(httpResponse.getEntity.getContent()).getLines().mkString
implicit val dbReader = Json.reads[DBInfo]
val dbList = (Json.parse(content) \ "items").as[List[DBInfo]]
dbList.foreach { dbI =>
if (dbI.Id == id)
if (dbI.DbName.contains("Test"))
println(dbI.DbName + " - " + dbI.Name)
else BadRequest("Not allowed")
else
BadRequest("ID not found")
}
id is the variable that holds the inputed ID by the user. Can someone tell me why the error and how to fix it? Thanks.
note: Please using import org.json4s.JsonAST or import play.api.libs.json
already got the answer. so this is how i did it:
case class databaseInfo(Id: String, DbName: String, Name: String)
class dbInfo{
def CheckDb(id: String): Option[String] = {
val httpClient = HttpClients.createDefault()
val httpResponse = httpClient.execute(new HttpGet("http://example.com"))
val content = fromInputStream(httpResponse.getEntity.getContent()).getLines().mkString
val envItems = (parse(content) \\ "items").children
for (items <- envItems) {
val dbItems = items.extract[databaseInfo]
if (dbItems.EnvId == Some(id)) {
if (equalsIgnoreCase(dbItems.DbName.mkString, "Test")) //do something
else //do something
}
}
None
}
}
Here is an approach using circe. You can navigate the JSON with a Cursor, and decode to a list of Environment using the Decoder[A] typeclass. Note that you work with Either[Failure, A] values.
import io.circe._
case class Environment(id: String, dbName: String, name: String)
implicit val environmentDecoder: Decoder[Environment] = Decoder.instance[Environment] {
json =>
for {
id <- json.downField("Id").as[String]
dbName <- json.downField("DbName").as[String]
name <- json.downField("Name").as[String]
} yield {
Environment(id, dbName, name)
}
}
// alternatively:
// implicit val environmentDecoder: Decoder[Environment] =
// Decoder.forProduct3[String, String, String, Environment]("Id", "DbName", "Name")(Environment.apply)
val text =
"""{
| "count": 3,
| "items": [{
| "Id": "fd0a9e5a",
| "DbName": "Xterior Prod",
| "Name": "XP"
| }, {
| "Id": "4158a1a6",
| "DbName": "Invidibi Pappear",
| "Name": "ISP"
| }, {
| "Id": "7e0c57046d3f",
| "DbName": "Multi Match Test",
| "Name": "MMP"
| }]
|}
""".stripMargin
val json = parser.parse(text).fold(_ => ???, json => json)
val res: Either[DecodingFailure, List[Environment]] = json.hcursor.downField("items").as[List[Environment]]
println(res)
// Right(List(Environment(fd0a9e5a,Xterior Prod,XP), Environment(4158a1a6,Invidibi Pappear,ISP), Environment(7e0c57046d3f,Multi Match Test,MMP)))
// or simply
// val res2 = parser.parse(text).right
// .flatMap(_.hcursor.downField("items").as[List[Environment]])
You can also use http4s' http4s-blaze-client and http4s-circe to do HTTP requests:
import org.http4s._
import org.http4s.circe._
import scalaz.concurrent._
val client = org.http4s.client.blaze.defaultClient
val fetchEnvironments: Task[List[Environment]] =
client.fetchAs[Json](Request(Method.GET, Uri.uri("http://example.com")))
.flatMap { json =>
json.hcursor.downField("items").as[List[Environment]].fold(
failure => Task.fail(failure),
xs => Task.now(xs)
)
}
val xs = fetchEnvironments.unsafePerformSync

Conditional JSON decoding based on a field value

I have a need to decode JSON into an elm type like below:
Type
type User = Anonymous | LoggedIn String
type alias Model =
{ email_id : User
, id : Id
, status : Int
, message : String
, accessToken : AccessToken
}
JSON Message 1
{
"status": 0,
"message": "Error message explaining what happened in server"
}
into type value
Model {
"email_id": Anonymous
, id: 0
, status: 0
, message: json.message
, accessToken: ""
}
JSON Message 2
{
"status": 1,
"email_id": "asdfa#asdfa.com"
"token": "asdfaz.adfasggwegwegwe.g4514514ferf"
"id": 234
}
into type value
Model {
"email_id": LoggedIn json.email_id
, id: json.id
, status: json.status
, message: ""
, accessToken: json.token
}
Decoder information
Above, "message" is not always present and email_id/id/token are always not present.
How to do this type of conditional decoding in elm
Json.Decode.andThen lets you do conditional parsing based on the value of a field. In this case, it looks like you'll first want to pull out the value of the "status" field, andThen handle it separately based on whether it is a 1 or 0.
Edit 2016-12-15: Updated to elm-0.18
import Html as H
import Json.Decode exposing (..)
type User = Anonymous | LoggedIn String
type alias Id = Int
type alias AccessToken = String
type alias Model =
{ email_id : User
, id : Id
, status : Int
, message : String
, accessToken : AccessToken
}
modelDecoder : Decoder Model
modelDecoder =
(field "status" int) |> andThen modelDecoderByStatus
modelDecoderByStatus : Int -> Decoder Model
modelDecoderByStatus status =
case status of
0 ->
map5
Model
(succeed Anonymous)
(succeed 0)
(succeed status)
(field "message" string)
(succeed "")
1 ->
map5
Model
(map LoggedIn (field "email_id" string))
(field "id" int)
(succeed status)
(succeed "")
(field "token" string)
_ ->
fail <| "Unknown status: " ++ (toString status)
main = H.div []
[ H.div [] [ decodeString modelDecoder msg1 |> Result.toMaybe |> Maybe.withDefault emptyModel |> toString |> H.text ]
, H.div [] [ decodeString modelDecoder msg2 |> Result.toMaybe |> Maybe.withDefault emptyModel |> toString |> H.text ]
]
emptyModel = Model Anonymous 0 0 "" ""
msg1 = """
{
"status": 0,
"message": "Error message explaining what happened in server"
}
"""
msg2 = """
{
"status": 1,
"email_id": "asdfa#asdfa.com"
"token": "asdfaz.adfasggwegwegwe.g4514514ferf"
"id": 234
}
"""