purescript-contrib / purescript-argonaut-codecs Goto Github PK
View Code? Open in Web Editor NEWJSON serialization and deserialization with Argonaut.
License: MIT License
JSON serialization and deserialization with Argonaut.
License: MIT License
I want to encode and decode values of type Ratio
. It is not supported yet. My offer is to encode and decode the same way as Aeson:
λ encode (3 % 5 ∷ Ratio Int)
"{\"numerator\":3,\"denominator\":5}"
My case specifically is related to argonaut-aeson-generic
and purescript-bridge
, so it is important to me that the encoding matches the Haskell side of my code. Since PureScript does not support orphan instances, I have no other way of making stuff work.
I can write a patch in no time.
The majority of decoders in 'Data.Argonaut.Decode' complain about errors without providing context for the error. For example Couldn't decode StrMap: Value is not a String
. When parsing a large JSON blob with a lot of different structures in it, it's quite hard to figure out what exactly is the problem. Some decoders though, do provide at least the value which triggered the error:
I can submit a PR doing the same for the rest of the decoders.
For example:
roundtrip :: forall a. (EncodeJson a, DecodeJson a) => a -> Either String a
roundtrip x = decodeJson <<< encodeJson
Then roundtrip (Just Nothing :: Maybe (Maybe Unit))
is Right Nothing
.
Is your change request related to a problem? Please describe.
In Haskell's Aeson library, the default contentsFieldName
is "contents"
:
https://hackage.haskell.org/package/aeson-2.2.1.0/docs/Data-Aeson.html#v:defaultTaggedObject
In argonaut-codecs
, it appears the default is "value"
:
Examples:
https://github.com/coot/purescript-argonaut-aeson-generic depends on argonaut-codecs
and attempts to provide compatibility between PureScript's Argonaut and Haskell's Aeson. It uses "contents"
. For most data types, it works great. However, it cannot decode/encode Either
to be compatible with Haskell's Aeson.
I have provided an example in this issue: coot/purescript-argonaut-aeson-generic#22
I have attempted to fix this on the Haskell side by overriding the instances to use "value"
, but haven't managed to make this work: https://github.com/peterbecich/purescript-bridge/blob/55e265b0d44c001357cd3aef3ac4a894128df12f/example/src/Types.hs#L96
Describe the solution you'd like
My request is to change "value"
to "contents"
here:
I have tested this: eskimor/purescript-bridge@55e265b This change fixes argonaut-aeson-generic
. Either
is encoded and decoded successfully.
Additional context
I am attempting to update Purescript Bridge and encountered this issue: eskimor/purescript-bridge#89
Thank you
Here is a simple modified example from docs with recursive newtype, and it is not deriving:
newtype AppUser = AppUser { name :: String, age :: Maybe Int, appUser :: AppUser }
derive instance newtypeAppUser :: Newtype AppUser _
derive newtype instance decodeJsonAppUser :: DecodeJson AppUser -- error
The value of decodeJsonAppUser is undefined here, so this reference is not allowed.
PureScript(CycleInDeclaration)
If it works as intended, probably it worth mentioning in the docs.
e.g. Nothing is not encoded as Null, the EncodeJson instance for Maybe is completely ignored when generically traversing a data structure containing a Maybe. This is because gEncodeJson' recursively invokes itself without using EncodeJson at all.
The result is, that generic encoding is very limited, because it is not possible to manually override parts of the process.
As gEncodeJson' only operates on spines, I don't really see how this can be fixed, unfortunately. Except for calling fromSpine on each recursion and checking whether there is an instance somehow and using it, if available. While this could theoretically work with some JS hackery, I believe fixing decoding is much harder, because you can not retrieve a value before having already decoded the JSON, but then it is already too late. (Except we could decode it twice if an instance is found.)
In any case - this is very hacky. Is this afterall because Haskell's generics are more powerful, that aeson does not have this problem?
Describe the bug
In Pursuit, the type of decodeJson
(as listed on the DecodeJson
typecclass) is shown as Json -> Either String a
. However, it is Json -> Either JsonDecodeError a
in the code.
Expected behavior
An updated documentation, this is the only error that I have come across thus far, however, there could be other errors.
We have an instances of EncodeJson and DecodeJson for NonEmpty Array
but not for NonEmptyArray
. Is there a reason for that?
If not, I'd be happy to take a shot at implementing, with two minor points:
encodeJsonNonEmptyArray
and decodeJsonNonEmptyArray
) are already in use. I propose renaming the existing ones to encodeJsonNonEmpty_Array
and decodeJsonNonEmpty_Array
. Would that break anything?encodeJsonNonEmptyFoldable
and decodeJsonNonEmptyFoldable
. That doesn't seem to have happened. Was there a reason it was avoided or did it just fall off the radar?I recently figured out how to get nested tuples to encode and decode as JSON arrays
e.g.
1 /\ "hi" /\ [ true ] /\ { a: 0 } -> [ 1, "hi", [ true ], { "a": 0 } ]
I did this for simple-json
but I'm pretty sure the approach would work here as well. Unexpectedly to me, the PR was not accepted. In light of that, I figured I'd ask if this would be a welcome addition before I put in the work.
Let's now discuss #73 )
For encoding Nothing as null for example, or Tuple a b as [a, b], ....
We could include options in Data.Argonaut.Options.Options, called userEncoding, userDecoding:
userEncoding :: GenericSignature -> GenericSpine -> Maybe Json
userDecoding :: GenericSignature -> Json -> Maybe GenericSpine
or something like this. genericEncodeJson'/genericDecodeJson' would then try those functions first on each recursion. Thus, the user can choose to override the encoding/decoding for certain signatures.
here
why L.insertAt 0 h t
??
it is equal to Just (h : t)
why not (h : t)
?
this would allow composing https://github.com/garyb/purescript-codec-argonaut/ and this lib
e.g. the purescript-codec-argonaut JsonCodec where some part is derived using class machinery? would be cool
Would it make sense to have instances for NonEmpty Array
and NonEmpty List
?
instance encodeJsonNonEmptyArray :: (EncodeJson a) => EncodeJson (NonEmpty Array a) where
encodeJson (NonEmpty h t) = encodeJson $ cons h t
instance encodeJsonNonEmptyList :: (EncodeJson a) => EncodeJson (NonEmpty List a) where
encodeJson (NonEmpty h t) = encodeJson $ cons h (toUnfoldable t)
Since the situation with generics is still up in the air. We can create a generics-rep version too. It'll need newtypes or something, to prevent the instances being orphaned, however (or use of the gEncodeJson
/ gDecodeJson
directly).
Following the discussion here #101
Could an EncodeJson
/ DecodeJson
for Map String a
be provided that convert from/to a javascript object instead of an array?
I think that this is the desired behavior in nearly all situations and when it isn't people can manually write their codec.
Here's the minimal example to reproduce:
newtype Foo = Foo
{fooId :: Int
,bazList :: List Baz
}
instance decodeJsonFoo :: DecodeJson Foo where
decodeJson json = do
obj <- decodeJson json
id <- obj .? "fooId"
bs <- obj .? "bazList"
pure $ Foo { fooId: id
, bazList : bs}
newtype Baz = Baz
{bazId :: Int
,bazOpt :: Maybe Int
}
instance decodeJsonBaz :: DecodeJson Baz where
decodeJson json = do
obj <- decodeJson json
bId <- obj .? "bazId"
bad <- obj .?? "bazOpt"
good <- foldJsonNumber Nothing (Just <<< floor) <$> obj .? "bazOpt"
pure $ Baz { bazId: bId
, bazOpt: bad
}
When each Baz in the list has Int value in bazOpt then "bad" works fine.
When some Bazes in the list has null in bazOpt field then "bad" give this error:
Couldn't decode List Value is not a Number.
"good" works fine even with nulls.
is getFieldOptional broken?
can add after 0.14, because cannot run tests now
instance decodeJsonCodePoint :: DecodeJson NonEmptyString where
decodeJson = decodeNonEmptyString
decodeNonEmptyString :: Json -> Either JsonDecodeError NonEmptyString
decodeNonEmptyString json =
note (Named "NonEmptyString" $ UnexpectedValue json)
=<< map (NonEmptyString.fromString) (decodeString json)
I heard this library now has automatic decoding into a plain record type, like simple-json is known for.
#46
It would be nice to see in the README that this library has this functionality, and to see an example of how to use it.
I guess it would be used something like this:
testDecode :: Maybe { a :: Int, b :: String }
testDecode = decodeJson """ { a: 123, b: "xyz" } """
Since an EncodeJson
instance was added for Records (#37), it is not possible to customize the default serialization of records without resorting to newtypes for everything. This is somewhat inconvenient for simple transformations.
It would be cool if I could pass a function to apply custom processing logic to the output JSON. In my case, I would like to be able to capitalize the names of the keys. Post-processing for keys would be a good first step, since I imagine it's fairly common to need to transform them into other formats (e.g. underscore separated).
It would be nice to export getFieldOptional (.??) from Data.Argonaut.Decode.
If this change is made, it would also be nice to export getFieldOptional from Data.Agronaut.
The function getFieldOptional
uses elaborateFailure
which displays the key for a value that couldn't be decoded. Unfortunately, getFieldOptional'
doesn't use this function, so please consider adding it.
See here:
If you're writing a codec for a value that cannot have a DecodeJson
instance declared for it, it seems sensible to write something like this:
traverse decodeCustom =<< obj .? "prop"
To get a Maybe Custom
success result. The idea being obj .? "prop"
result is a Maybe Json
and then decodeCustom
does the Json -> Custom
step. However, the decodeJsonJson
instance always succeeds, as null
is a decodeable value, so you end up with Just null
as the response, and then decodeCustom
fails as it's probably expecting something else.
If you're using instances everywhere this works fine, as the custom decoder would run "inside" the Maybe
decoder, and failing there gives you a Nothing
result.
Maybe it was a faulty assumption on my part that null
values would be considered Nothing
? It took me a really long time to track this down though. 😭
Is your change request related to a problem? Please describe.
It's more of a question than a change request per se. I was naively trying to decode a JSON object by giving it the type Map String T
(for some irrelevant T
). This didn't work, because the decodeJson
instance for Map
expects an Array
(of pairs, I guesss).
Isn't it a bit more obvious to expect a JSON object instead of an array of pairs?
Would a PR to implement EncodeJson
/DecodeJson
for Natural
be welcome?
I noticed when you went from v0.6.1 to v1.0.0 of the library the function decodeMaybe
remains in the codebase but it is no longer exported.
The decodeMaybe
function now seems to be orphaned as it's not called anywhere else in the code as far as I can tell. Was this intentional or just an oversight?
Thanks,
Chris.
The library purescript-simple-json has instances to encode and decode records using RowToList. Would it be a good idea to add these also to Argonaut? I'm willing to make a PR, but I'd like to know if it would be useful first.
Looking at unordered collections, these types should have instances as well:
After speaking with @davezuch I realized (at his prompting) that the functions and operators for .:?
and .!=
are unnecessary and can be replaced without losing any functionality in this library.
For example, given this type:
newtype User = User
{ name :: String
, age :: Maybe Int
, team :: String
}
derive instance newtypeUser :: Newtype User _
derive newtype instance showUser :: Show User
these two instances are identical:
instance decodeJsonUser :: DecodeJson User where
decodeJson json = do
obj <- decodeJson json
name <- obj .: "name"
age <- obj .:? "age"
team <- obj .:? "team" .!= "Red Team"
pure $ User { name, age, team }
instance decodeJsonUser :: DecodeJson User where
decodeJson json = do
obj <- decodeJson json
name <- obj .: "name"
age <- obj .: "age"
team <- obj .: "team" <|> pure "Red Team"
pure $ User { name, age, team }
Because the Maybe
instance for .:
already covers the case covered by .:?
, and the functionality of .!=
(providing a default value for a type which may not exist in the Json
, but must exist in the decoded type) is covered by the use of Alternative
: <|> pure default
. In fact, this is even better, because it introduces to folks the ability to have fallbacks via Alternative
rather than lead them to use an overly-specific operator.
> decodeJson =<< jsonParser """{ "name": "Tom", "age": 55, "team": "Blue Team" }""" :: Either String User
(Right { age: (Just 55), name: "Tom", team: "Blue Team" })
> decodeJson =<< jsonParser """{ "name": "Tom", "age": 55 }""" :: Either String User
(Right { age: (Just 55), name: "Tom", team: "Red Team" })
> decodeJson =<< jsonParser """{ "name": "Tom", "age": null }""" :: Either String User
(Right { age: Nothing, name: "Tom", team: "Red Team" })
Side note: I'm not sure that .:!
needs to exist either. Its sole difference from .:
applied to Maybe
is that it will error on a key present with a null
value instead of decoding to Nothing
. I'm not sure when that would ever be the desired behavior. But that's a question for another day.
I'd like to open a PR which deprecates .:?
and .!=
and standardizes on just .:
and <|> pure default
.
The first thing I always need to do when using argonaut in a project is to write functions fromJson
function which composes parseJson
and decodeJson
and toJson
composing encodeJson
and stringify
:
fromJson :: forall t. DecodeJson t => String -> Either JsonDecodeError t
fromJson = parseJson >=> decodeJson
toJson :: forall t. EncodeJson t => t -> String
toJson = encodeJson >>> stringify
So why not add them to argonaut
? They could also be named parseAndDecode
/ encodeAndStringify
or sth else, I don't really mind. Though I find fromJson
and toJson
just nicely minimalistic.
I have created a pr for it in #109 .
Update:
changed type variable from json
to t
in the first case to avoid confusion.
I'm struggling to transcode a record with native types and a single foreign Json
entry for round trip to localStorage
. My naive untested yet solution is:
-- I want to transcode this
newtype Event = Event { id: String, data: Json }
newtype UnsafeJson a = Unsafe Json a
instance encodeJsonUnsafe Json :: EncodeJson (Unsafe Json a) where
encodeJson = unsafeCoerce
instance decodeJsonUnsafe Json :: DecodeJson (Unsafe Json a) where
decodeJson = Left <<< unsafeCoerce
newtype
that transcode without necessarily doing anything smart? I know I can manually
stringify
/parse
but it wouldn't work with clients of the codecs (specifically https://github.com/texastoland/purescript-localstorage/blob/refactor/src/DOM/WebStorage/JSON.purs).purescript-deprecated/purescript-generics#21
I recently started to work on #5, so I'll adopt it along the way while it's getting merged in.
Is there a way to easily encode and decode records?
For instance, I have the following type:
newtype Register = Register { email :: String
, password :: String
}
I'd like to use purescript-generics for the encoding and decoding like below:
derive instance genericRegister :: Generic Register
instance encodeJsonRegister :: EncodeJson Register where
encodeJson = gEncodeJson
But when running the following code:
let register = Register { email: "[email protected]", password: "foobar" }
in show $ encodeJson res
I get the following output: {"values":[{"password":"foobar","email":"[email protected]"}],"tag":"Register"}
I was expecting the output to look like this: {"password":"foobar","email":"[email protected]"}
I tried to change the EncodeJson instance like the following:
instance encodeJsonRegister :: EncodeJson Register where
encodeJson (Register reg) = gEncodeJson reg
But that just gave me the following compiler error:
Error found:
Error in value declaration encodeJsonRegister:
Error at /opt/src/src/Main.purs line 44, column 1 - line 46, column 1:
Error in module Main:
No instance found for
Data.Generic.Generic { password :: String
, email :: String
}
The error makes sense, but I was hoping it would be easier to define encodeJson
for records. Is there an easy way? I don't want to have to write out something like this for every type:
instance encodeJsonRegister :: EncodeJson Register where
encodeJson (Register reg)
= "email" := reg.email
~> "password" := reg.password
~> jsonEmptyObject
Maybe I've just been spoiled by aeson
:-(
This package needs an update because the typelevel-prelude
dependency has had a breaking release (to address a re-export issue in the compiler, purescript/#3502). The relevant change is in typelevel-prelude/#48. Ideally this update can be done without requiring a breaking release of this library by ensuring the package works with both 4.x.x and 5.x.x of typelevel-prelude.
Issue purescript/#3650 tracks the various libraries affected by the change.
Is your change request related to a problem? Please describe.
Can we support optional fields to Maybe? In particular, treat undefined values as Nothing.
Both should be decode to { a: Nothing }
{}
{"a": null}
Describe the solution you'd like
I propose adding a new
class DecodeJsonField a where
decodeJsonField :: Maybe Json -> Maybe (Either JsonDecodeError a)
and using it within the decoding.
I am also of the opinion of encoding Nothing
should encode to an undefined field - but that can be a separate matter.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.