xyncro / chiron

JSON for F#
https://xyncro.tech/chiron
MIT License
175 stars 41 forks source link

Q: how to handle null in Json #41

Open gsscoder opened 9 years ago

gsscoder commented 9 years ago

When Json is something like

"{"query":{"count":1,"created":"2015-08-25T13:35:55Z","lang":"en-US","results":{"quote":{"symbol":"ORCL","AverageDailyVolume":"15153400","Change":"+0.00","DaysLow":null,"DaysHigh":null,"YearLow":"35.14","YearHigh":"46.71","MarketCapitalization":"156.45B","LastTradePriceOnly":"36.08","DaysRange":null,"Name":"Oracle Corporation Common Stock","Symbol":"ORCL","Volume":"2200","StockExchange":"NYQ"}}}}"

When I try to parse with Json.read DaysLow for example I get:

> System.Exception: couldn't use lens (<fun:totalLensPartialIsomorphism@98>, <fun:totalLensPartialIsomorphism@99-1>) on json 'Null null'
   at Microsoft.FSharp.Core.Operators.FailWith[T](String message)
   at Network.Yahoo.Finance.parseResponse(String json) in H:\YFinance.fs\src\YFinanceFs\Finance.fs:line 58
   at Network.Yahoo.Finance.getStockQuoteAsync@88-1.Invoke(String _arg1) in H:\YFinance.fs\src\YFinanceFs\Finance.fs:line 88
   at Microsoft.FSharp.Control.AsyncBuilderImpl.args@835-1.Invoke(a a)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.FSharp.Control.AsyncBuilderImpl.commit[a](Result`1 res)
   at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
   at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
   at Network.Yahoo.Finance.getStockQuote(String symbol) in H:\YFinance.fs\src\YFinanceFs\Finance.fs:line 105
   at <StartupCode$FSI_0003>.$FSI_0003.main@()

Using Json.readOrDefault "DaysLow" String.Empty didn't solved the problem.

If json contains a null value I'd like to supply a default. Is this possible?

Thank you.

kolektiv commented 9 years ago

Hmmm, interesting. I'll take a look at that this evening and work out what's happening there. :)

gsscoder commented 9 years ago

:+1:

kolektiv commented 9 years ago

Trying things out, I'm looking at something like this, which seems to work - but I'm not sure what code you're actually running! Feel free to post up more repro if this isn't helpful :)

    let txt = """{"query":{"results":{"low":null}}}"""
    let json = Json.parse txt

    let low =
            idLens
       <-?> Json.ObjectPIso
       >??> mapPLens "query"
       <??> Json.ObjectPIso
       >??> mapPLens "results"
       <??> Json.ObjectPIso
       >??> mapPLens "low"
       <??> Json.StringPIso

    let read =
            function | Some v -> v
                     | _ -> "default"
        <!> Json.tryGetLensPartial low

    let low =
        match read json with
        | Value low, _ -> low
        | _ -> failwith "unreachable"
kolektiv commented 9 years ago

(On a side note, I think it's worth doing something which combines Json.ObjectPIso and mapPLens x, as that's a common combination. I'll look at that in the next release too, which will be shortly, to bring Chiron in to line with common naming convention for lenses)

gsscoder commented 9 years ago

Thanks for comments and sample, @kolektiv. But the issue I've reported was before I've traversed json using lens. The exception occur when Json.deserialize invoke StockQuote.FromJson. Some time some field from Yahoo service are null, what I'd like to do is being able to supply a default in such case.

This was the original parseResponse before partial lenses:

let private parseResponse json =
    match Json.parse json with
    | Object values ->
      match values |> Map.find "query" with
      | Object values ->
        match values |> Map.find "results" with
        | Object values -> 
          let node = values |> Map.find "quote"
          match node with
          | Array quotes ->
            quotes |> List.map (Json.deserialize : _ -> StockQuote)
          | Object _ ->
            [Json.deserialize node]
          | _ -> []
        | _ -> []
      | _ -> []
    | _ -> []

I don't know if I completely understood your reply... Do I have to define a Json traversal for each field that could be null? There's nothing more terse I can't do with Json.read in StockQuote.FromJson?