7 ms·
I really wish people would stop spreading the meme that decoding JSON in Elm is "hard". Yes, Haskell allows you to automatically decode/encode datatypes, but th
by fbonetti 9y ago
I really wish people would stop spreading the meme that decoding JSON in Elm is "hard". Yes, Haskell allows you to automatically decode/encode datatypes, but this only works in the simplest of cases. For example, if your backend returns a JSON object with snake-cased fields, but your model has camel-cased fields, `instance ToJSON Person` won't work; you'll have to write a custom decoder. The automatic decoders/encoders in Haskell only work if the shape of your JSON perfectly matches your record definition.
Writing decoders in Elm is not hard. It's manual. It's explicit. It forces you to specify what should happen if the JSON object has missing fields, incorrect types, or is otherwise malformed. There's a slight learning curve and it can be time consuming at first, but it guarantees that your application won't blow up at runtime because of some bad data. Because of this, JSON decoding is frankly one of my favorite parts about Elm.
Typescript, on the other hand, offers no such guarantee. If you write a function that takes an Int and you accidentally pass it a String from a JSON response, your app will blow up and there's the nothing the compiler can do to help you. Personally, I'd rather write JSON decoders than have my app blow up because of a silly mistake.
- empath75 9y ago> Writing decoders in Elm is not hard. At some point, after enough people tell you that they can't figure out how to do something, one should admit that it is difficult.
- Skinney 9y agoOr that the documentation can be improved, or the API could be improved, or a number of other things. But really, JSON encoding/decoding in Elm boils down to specifying what the type of an object's field is. If that is difficult, interfaces and classes must be a nightmare.
- empath75 9y agoHere is what people are comparing it to, in terms of difficulty: json.loads('foo.json') I spent 3 days about 6 months ago, off and on, trying to figure out json decoding in elm, and gave up on the language entirely. It wasn't a matter of simply specifying types. The syntax is confusing the compiler isn't any help. I had very little difficulty working with anything else in Elm, so I think there is a lot of work to be done there. If there were a more painless way to do it, there are a lot of api's I'd love to write front ends for in elm.
- Skinney 9y agojson.loads('foo.json') (what is that? python?) requires that the json matches your types exactelly. This, at least for me, is rarely the case. And yes, it is. Here is code from my own project: Json.map3 HintTemplate (Json.field "ID" Json.int) (Json.field "Text" Json.string) (Json.field "Image" Json.string) Where HintTemplate is a record type containing an integer id, a string text and a image url. What about this is difficult?
- Tarean 9y agoThat's reasonably similar to handwritten haskell json parsing: instance FromJson HintTemplate where parseJson = withObject $ \o -> HintTemplate <$> o .: "ID" <*> o .: "Text" <*> o .: "Image" I wrote <$> and <> out but there is a definition like map3: liftA3 f a b = f <$> a <*> b This works for all applicatives, though. Is it normal for elm libraries to copy paste code like the map3 definition?
- Skinney 9y agoNot sure what you mean. It's normal that different packages have their own version of a given function, like List.map3, Json.map3 etc. But the implementation is different, so it's not copy-paste. It has to be this way in cases where generic types is not enough and something like interfaces/typeclasses is required.
- fbonetti 9y agoElm's primary selling point is "no runtime exceptions". The subheader on Elm's homepage even says this[1]: Generate JavaScript with great performance and no runtime exceptions. Type safety doesn't come for free. If you want to build an application that doesn't have runtime exceptions, you have to specify what external data should look like, how to translate that data into a native datatype, and what to do if that expectation fails. With this in mind, you can't just write `JSON.parse(data)` and expect everything to work. What if your data has a different shape? What if it's missing fields? What if field "foo" is a String instead of an Int? Here's a short example in Typescript: import fetch from "node-fetch" interface Post { user_id: number, id: number, title: string, body: string } const postUrl = 'https://jsonplaceholder.typicode.com/posts/1' const show = function(post: Post) { console.log(post.user_id) console.log(post.id) console.log(post.title) console.log(post.body) } function getJSON(): Promise<Post> { return fetch(postUrl).then(x => x.json()) } getJSON().then(show) Do you see the bug? `user_id` is supposed to be `userId`, yet this example will compile without an issue. When you run it, `post.user_id` will be undefined, even though our interface clearly states that a Post most have a `user_id` field of type `number`. The compiler won't catch this even with `--strictNullChecks` enabled. Everything in programming is about tradeoffs. If you don't care about strong type correctness, use something like Typescript. If you do care, Elm is a great choice. [1]: http://elm-lang.org/ http://elm-lang.org/
- Albert_Camus 9y agoAuthor here. I agree with your points, and in my article I specifically mention that there are benefits to some of the "hardness" of certain tasks in Elm (type-safety in the case of JSON decoding). But to claim that JSON decoding in Elm is not significantly more difficult than it is in JavaScript would be misleading. A front end developer that has only written JS will be surprised when he/she cannot just drop in the equivalent of JSON.parse() and get an Elm value out of it. I call it "hard" because there is a bit of a learning curve, and it does require some thought, and quite frankly it takes quite a bit of time if you have a large application like we do. Moreover, I am not complaining. And I do not think people should be. As I said in the article, the tradeoff is worth it.
- somenewacc 9y agoYou don't need to write the decoder boilerplate manually to get all those benefits. For example, here's how you rename a JSON field while serializing/deserializing a data type in Rust: https://play.rust-lang.org/?gist=1b382bc1572858841d5e392435de73f4&version=stable https://play.rust-lang.org/?gist=1b382bc1572858841d5e392435d... You just annotate the field with #[serde(rename = "..")]. Here is a list of such annotations https://serde.rs/field-attrs.html https://serde.rs/field-attrs.html Serde is also generic in the serialization format; the example I linked uses serde_json, and was adapted from its README here https://github.com/serde-rs/json https://github.com/serde-rs/json
- MaxGabriel 9y agoSame for Haskell. This package provides common translations like snake_case to CamelCase: https://www.stackage.org/haddock/lts-9.0/aeson-casing-0.1.0.5/Data-Aeson-Casing.html https://www.stackage.org/haddock/lts-9.0/aeson-casing-0.1.0.... Giving you automatic encoders/decoders like so: instance ToJSON Person where toJSON = genericToJSON $ aesonPrefix snakeCase instance FromJSON Person where parseJSON = genericParseJSON $ aesonPrefix snakeCase And the implementation of that package is like 4 simple lines for snake case; it's totally doable on your own for whatever you need https://github.com/AndrewRademacher/aeson-casing/blob/260d18d62bbb57240b0b7b1e908c8a896ffd8d77/src/Data/Aeson/Casing/Internal.hs#L34-L40 https://github.com/AndrewRademacher/aeson-casing/blob/260d18... I haven't had to do snake_case to CamelCase with Aeson before, but I have dropped a prefix before, like "userName" -> "name", "userAge" -> "age", and it was pretty easy and well supported. Also I would note that this isn't as big of a deal for Haskell and Rust, because they're primarily backend languages, so they're more often sending out JSON in whatever form they please, rather than consuming it. In my experience the main consumers (Javascript on the web, Objective-C on iOS and Java on Android) use CamelCase anyway, so there's a natural compatibility.
- wraithm112 9y agoWith respect to the snake-cased fields issue, it's actually not that hard to do that with Generic in Haskell. data Person = Person { personFirstName :: Text , personLastName :: Text } deriving (Generic) instance ToJSON Person where toJSON = genericToJSON $ aesonPrefix snakeCase instance FromJSON Person where parseJSON = genericParseJSON $ aesonPrefix snakeCase Which produces messages like: { "first_name": "John", "last_name": "Doe" }
- KirinDave 9y agoIt's not so much that writing decoders is hard it's that writing robust parsers without applicative is quite laborious, once you're used to having them.
- deleted 9y ago[deleted]
- neerajdotname2 9y agoI found it hard. I have been learning Elm for a few weeks. I have a background in Ruby and in JavaScript. I have a repo where I am trying difference cases. https://github.com/bigbinary/road-to-elm https://github.com/bigbinary/road-to-elm