Giter VIP home page Giter VIP logo

json's Introduction

JSON in Elm

This package helps you convert between Elm values and JSON values.

This package is usually used alongside elm/http to talk to servers or ports to talk to JavaScript.

Example

Have you seen this causes of death table? Did you know that in 2002, war accounted for 0.3% of global deaths whereas road traffic accidents accounted for 2.09% and diarrhea accounted for 3.15%?

The table is interesting, but say we want to visualize this data in a nicer way. We will need some way to get the cause-of-death data from our server, so we create encoders and decoders:

module Cause exposing (Cause, encode, decoder)

import Json.Decode as D
import Json.Encode as E


-- CAUSE OF DEATH

type alias Cause =
  { name : String
  , percent : Float
  , per100k : Float
  }


-- ENCODE

encode : Cause -> E.Value
encode cause =
  E.object
    [ ("name", E.string cause.name)
    , ("percent", E.float cause.percent)
    , ("per100k", E.float cause.per100k)
    ]


-- DECODER

decoder : D.Decoder Cause
decoder =
  D.map3 Cause
    (D.field "name" D.string)
    (D.field "percent" D.float)
    (D.field "per100k" D.float)

Now in some other code we can use Cause.encode and Cause.decoder as building blocks. So if we want to decode a list of causes, saying Decode.list Cause.decoder will handle it!

Point is, the goal should be:

  1. Make small JSON decoders and encoders.
  2. Snap together these building blocks as needed.

So say you decide to make the name field more precise. Instead of a String, you want to use codes from the International Classification of Diseases recommended by the World Health Organization. These codes are used in a lot of mortality data sets. So it may make sense to make a separate IcdCode module with its own IcdCode.encode and IcdCode.decoder that ensure you are working with valid codes. From there, you can use them as building blocks in the Cause module!

Future Plans

It is easy to get focused on how to optimize the use of JSON, but I think this is missing the bigger picture. Instead, I would like to head towards this vision of data interchange.

json's People

Contributors

emilgoldsmith avatar evancz avatar panthershark avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

json's Issues

Using == on Json.Decode.Values can return `True` for values that aren’t equal

SSCCE

> import Json.Encode as JE
> JE.object [ ("a", JE.list identity []) ] == JE.object []
True : Bool
> JE.object [ ("a", JE.object []) ] == JE.object []
True : Bool
> JE.object [ ("a", JE.null) ] == JE.object [ ( "a", JE.null ), ( "b", JE.null ) ]
True : Bool

Edit: This Discourse post by Evan explains that (==) shouldn’t be used with JSON values from elm/json and an idea for improving this in the future: https://discourse.elm-lang.org/t/function-equality/7538/24

Edit2: It’s even documented that one shouldn’t use (==) with JSON values: https://package.elm-lang.org/packages/elm/core/latest/Basics#equality 😅 But who reads the docs for (==), right?

Json.Decode.index checks for too large index but not too small

If the index given to Json.Decode.index is too large we get this message:

> JD.decodeString (JD.index 1 JD.int) """[1]""" |> Result.mapError JD.errorToString
Err ("Problem with the given value:\n\n[\n        1\n    ]\n\nExpecting a LONGER array. Need index 1 but only see 1 entries")
    : Result String Int

In other words, there is an out-of-bounds check that produces that nice message. But the index can also be out-of-bounds by being negative. In that case, there is currently no check for this, so array[-1] (which is undefined) is attempted to be decoded:

> JD.decodeString (JD.index -1 JD.int) """[1]""" |> Result.mapError JD.errorToString
Err ("Problem with the value at json[-1]:\n\n    undefined\n\nExpecting an INT")
    : Result String Int

That’s still an error message, but not quite as nice. But using -1 doesn’t always fail:

> JD.decodeString (JD.index 1 (JD.succeed ())) """[1]""" |> Result.mapError JD.errorToString
Err ("Problem with the given value:\n\n[\n        1\n    ]\n\nExpecting a LONGER array. Need index 1 but only see 1 entries")
    : Result String ()
> JD.decodeString (JD.index -1 (JD.succeed ())) """[1]""" |> Result.mapError JD.errorToString
Ok () : Result String ()

I think the error message could be nicer for a negative index. Similar to how oneOf has a nice error message for an empty list:

> JD.decodeString (JD.oneOf []) """true""" |> Result.mapError JD.errorToString
Err ("Ran into a Json.Decode.oneOf with no possibilities!")

Maybe something like this: "Ran into a Json.Decode.index with a negative index: -1".

In summary:

  • If there’s an upper bounds check, why is there no lower?
  • The error message for negative index could be better.
  • An out-of-bounds index should always fail (not just when too large) for consistency.

Note: I only noticed this because I’m re-implementing this package in Elm as a learning exercise. I’ve never accidentally passed a negative index in real code.

Decode.andMap?

Is there a reason the Decode module doesn't expose an andMap? I found myself needing this in an app I'm working on, and ended up just defining it inline:

decodeAndMap = Decode.map2 (\f x -> f x)

...not a big deal to have to do that, but was there a reason for the omission? Thoughts on including it?

Decoder error even-though it's correct

I have the following Type called Message with the following code

module Types.Message exposing (Message, decode, decodeList)

import Json.Decode as Decode exposing (Decoder, Error, list)
import Json.Decode.Pipeline exposing (required)


type alias Message =
    { username : String
    , content : String
    }


decode : Decoder Message
decode =
    Decode.succeed Message
        |> required "username" Decode.string
        |> required "content" Decode.string

decodeList : Decoder (List Message)
decodeList =
    list decode

I try to decode using something like this

decodeMessage : Decode.Value -> Result Error (List Message)
decodeMessage messageJson =
    Decode.decodeValue
        Message.decodeList
        messageJson

I found out that it works perfectly in REPL

> decodeString decode "{\"username\": \"mantap\", \"content\": \"lalui\"}"
Ok { content = "lalui", username = "mantap" }
    : Result Error Message

However, when I run the Webpack build using the elm-webpack-loader in Electron environment I found the following error

Problem with the value at json[0]: "{\"content\": \"Budi\", \"username\": \"UHU\"}" Expecting an OBJECT with a field named `content`

Any idea why?

Encoding an object with circular dependencies throws an Error.

When one is passing an object with circular dependencies from JS to Elm and then calls Json.Encode.encode on the Value you get a runtime Error from within Elm.

Of course the source of the error is from JS land but it would be nice if Elm gave a better Error message and log something like "<Json with circular references>" rather than a stack trace. Maybe the JSON.stringify call could be put in a try/catch or something. It's not a huge issue but I have encountered this error multiple times when people were printing stuff for debugging and it would be helpful if Elm told them the source of their error right away.

SSCCE:

-- Elm
module Main exposing (..)

import Browser
import Html exposing (text)
import Json.Encode as JE


type alias Model =
    {}

type Msg
    = NoOp

init : JE.Value -> ( Model, Cmd Msg )
init flags =
  let _ = JE.encode 2 flags -- will cause an infinite recursion
  in
    ( {}, Cmd.none )

main : Program JE.Value Model Msg
main =
    Browser.document
        { view = \_ -> { title="SSCCE", body = [  text "this will crash" ] }
        , init = init
        , update = \msg model -> (model, Cmd.none)
        , subscriptions = always Sub.none
        }
// JS
Elm.Main.init({ flags: window });

Json equality for oneOf decoders always returns true

The oneOf decoder stores an Elm list of decoders, but when comparing for equality these lists are compared with _Json_listEquality, which compares JS arrays. The result is that oneOf decoders always compare as equal.


I noticed this when a custom event decoder was not being replaced as I expected. Here's an SSCCE that demonstrates such a case.

module Main exposing (main)

import Browser
import Html exposing (Html)
import Html.Events
import Json.Decode


main : Program () Int Int
main =
    Browser.sandbox
        { init = 0
        , view = view
        , update = always
        }


view : Int -> Html Int
view clicks =
    Html.button
        [ Html.Events.on "click"
            -- good:
            --(Json.Decode.succeed (clicks + 1))
            --
            -- bad:
            (Json.Decode.oneOf [ Json.Decode.succeed (clicks + 1) ])
        ]
        [ Html.text (String.fromInt clicks)
        ]

Json.Json!

Similar to Bytes.Bytes and Bytes.Encode, Bytes.Decode.

I would be nice to have Json.Json type and remove Encode.Value and Decode.Value.

I was confused what is difference between Encode.Value and Decode.Value.
And then I check the source code and realize that it is alias.

I would say that having Json.Json type will make very clear - it is a JSON!

What do you think?

Chooses a more positive example

It feels odd that elm/json talks about deaths in its introduction example. Picture a person which just lost one of their relatives.

Alternative ideas:

  • Temperature data in different countries
  • Discrimination in our society

Runtime exception when using Decode.dict on a JSON created with Object.create(null)

SSCCE

result = Json.Decode.decodeValue (Json.Decode.dict Json.Decode.string) jsValue

when jsValue has been created in JavaScript using Object.create(null) and sent through flags or a port.

Attempting the decoding above results in a runtime exception with this error:

Uncaught TypeError: value.hasOwnProperty is not a function
    at _Json_runHelp (VM37 workspace:1461)
    at _Json_runHelp (VM37 workspace:1478)
    at Function.f (VM37 workspace:1400)
    at A2 (VM37 workspace:56)
    at $author$project$Main$init (VM37 workspace:10540)
    at VM37 workspace:8540
    at VM37 workspace:20
    at _Platform_initialize (VM37 workspace:1877)
    at VM37 workspace:3973
    at Object.init (VM37 workspace:20)

I created an Ellie here that you can interact with: https://ellie-app.com/8ZWrTfBzWRZa1

Additional Details

  • Elm: 0.19.1
  • elm/json: 1.1.3
  • Browser: Brave

Object.create(null) is sometimes used to create a JavaScript dictionary . The difference with {} is that {} contains fields from the Object prototype, where Object.create(null) has no prototype and no fields at all.

> ({}).toString
[Function: toString]
> Object.create(null).toString
undefined

This distinction, at least in JavaScript, can be important when you use this value as a set and only care about whether the field exists.

This distinction is irrelevant when decoding, but this runtime error makes it impossible to send values that in JS-land needed to be created this way without somehow cloning them beforehand.

Encode.dict & Decode.dict are asymmetrical

Hello!

I'm working with dictionaries of character-based keys. Encoding them is straight-forward, as I simply have to convert keys from characters to strings. No problem!

Writing the decoder for this kind of dictionary was more problematic, as I don't have the option convert keys back into characters.

Encode.dict : (k -> String) -> (v -> Encode.Value) -> Dict k v -> Encode.Value

Decode.dict : Decoder a -> Decoder (Dict String a)

I am working around this, but I'd love for the solution to be incorporated into Decode.dict, to avoid the additional passes through the data.

Proposed solution

I'd really like these functions to be symmetrical, in the sense that the dictionary-decoder takes a function to convert the string-key into a comparable value in the same way the encoder requires me to convert the comparable keys into strings.

Perhaps like this:

Decode.dict : (String -> comparable) -> Decoder a -> Decoder (Dict comparable a)

To spare you the time, I could do this implementation for you, but let me know what you think when you have the time ☀️

Add a function to create a Decoder from an Error value

Suggestion: Add a function (perhaps called error) that is like fail, except it receives an Error value instead of a String as its argument.

This would be useful in functions that have to call decodeValue inside an andThen continuation.

Here's an example of a case where such a function would be nice (I defined a sub-optimal version of error so it would compile):
https://ellie-app.com/bgcTCxqmSKWa1

Given that the Error type constructors are already exposed from this module, I don't see a good reason not to allow this sort of thing, but maybe there's a downside to this that I haven't considered.

Minor documentation hiccup

The docs for Json.Decode.errorToString still says “we cannot have any HTML dependencies in elm/core”, this should probably be changed to “we cannot depend on elm/html”.

Using `==` on `Json.Decode.Value`s can throw runtime exceptions

SSCCE

elm repl
> import Json.Decode as JD
> import Json.Encode as JE
> JD.decodeString (JD.index -1 JD.value) "[]" |> Result.map (\v -> JE.object [("a", JE.null)] == v)
TypeError: Cannot read property 'a' of undefined
> JE.list identity [ JE.object [ ("a", JE.null) ] ] == JE.list JE.int []
TypeError: Cannot read property 'a' of undefined
> JE.object [ ("a", JE.object [ ( "a", JE.null ) ] ) ] == JE.object []
TypeError: Cannot read property 'a' of undefined
> JE.object [ ("$", JE.string "Set_elm_builtin" ) ] == JE.object [ ("$", JE.string "Set_elm_builtin" ) ]
TypeError: Cannot read property '$' of undefined

The above examples fail because the value on the right side of == is undefined.

Here’s an Ellie by @mpizenberg: https://ellie-app.com/cLXHgSHm8yTa1

Notes

The problem seems to be that _Utils_eqHelp assumes both its arguments are of the same type/shape. In general that’s true in Elm, but not for Json.Decode.Value. It’s a wrapper around any value, which might not be the same on both sides.

Edit: This Discourse post by Evan explains that (==) shouldn’t be used with JSON values from elm/json and an idea for improving this in the future: https://discourse.elm-lang.org/t/function-equality/7538/24

Edit 2: Apparently this is even documented! 🤦 https://package.elm-lang.org/packages/elm/core/latest/Basics#equality
Though these crash for a different reason (“the implementation is not at all made for Json.Decode.Value”) than mentioned in those docs.

Decoding an object with an absent field

I can't understand how to write a Maybe-decoder which would succeed upon either an absent field or a null-key:

> Decode.decodeString (Decode.oneOf [Decode.nullable (Decode.field "foo" Decode.int), Decode.field "foo" (Decode.nullable Decode.int)]) """{"foo": 123}"""
Ok (Just 123) : Result Decode.Error (Maybe Int)
> Decode.decodeString (Decode.oneOf [Decode.nullable (Decode.field "foo" Decode.int), Decode.field "foo" (Decode.nullable Decode.int)]) """{"foo": null}"""
Ok Nothing : Result Decode.Error (Maybe Int)
> Decode.decodeString (Decode.oneOf [Decode.nullable (Decode.field "foo" Decode.int), Decode.field "foo" (Decode.nullable Decode.int)]) """{}"""           
Err (OneOf [OneOf [Failure ("Expecting null") <internals>,Failure ("Expecting an OBJECT with a field named `foo`") <internals>],Failure ("Expecting an OBJECT with a field named `foo`") <internals>])
    : Result Decode.Error (Maybe Int)

Why does it fail in the last case? How do I make it work?

Thank you.

elm/files is broken in IE

Related to elm/file#8

Using the latest version of elm/file with the lastest version of elm/json causes file selection to stop working. The root cause is a bad piece of code that appears to have been added for node.js environments.

Detect attempts to install packages for an older version of elm

At the moment there's a kernel panick

$ elm --version
0.19.0

$ elm-json --version
elm-json 0.2.0

$ RUST_BACKTRACE=1 elm-json install krisajenkins/elm-dialog
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:345:21
stack backtrace:
   0: start
   1: start
   2: start
   3: start
   4: start
   5: start
   6: start
   7: start
   8: start
   9: start
  10: start
  11: start
  12: start
  13: start
  14: main
  15: start
  16: main

krisajenkins/elm-dialog has never been updated to work with 0.19

Invalid list equality check.

Issue

There seems to be an issue with list equality check. It looks like the function functions want to work with lists (JS arrays) to get a length of a list and then compare each element one by one. But it actually gets objects with a list representation. Thus it always goes to return true; branch.

This creates another issues.

image

Solution

One way to solve it is to call _Json_listEquality function recursivelly.

Consider `Basics.eq` rather than `===` for the `succeed` case in `_Json_equality`

I was looking at the code which tests JSON decoders for equality, and thought there might be an improvement available to the code quoted below.

https://github.com/elm-lang/json/blob/eddab8e83118accb3bae60c259ad154c40228fe3/src/Elm/Kernel/Json.js#L345-L347

The case I'm thinking about is the _1_SUCCEED case, where the msg may be any Elm value.

The quoted code is testing for equality using Javascript's ===. Unless I'm missing something, it would seem better to use Basics.eq here, since it will handle some Elm values more accurately than Javascript's === will.

Of course, there may be any number of good reasons why you're sticking to === here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.