patricoferris / ppx_deriving_yaml Goto Github PK
View Code? Open in Web Editor NEWOCaml types to Yaml types and back again
License: ISC License
OCaml types to Yaml types and back again
License: ISC License
Currently there is no way to specify your own names for keys in the `O of (string, value) list
type (which variants and records transform to). For example:
type t = { camel : int } [@@deriving yaml]
Will only read/produce YAML of the form
camel: 1
We can't have Camel
or cam-el
as these are not allowed as record field names. The same problem exists for variants like type t = Camel of int
.
Add a name
attribute to the ppx in order to specify a transformation -- exactly like ppx_deriving_yojson
:)
No support for of_yaml
in .mli
files i.e. it doesn't produce the type signature!
That would be nice to be able to do something like:
type x = { a: int; b: int } [@@deriving yaml]
type t = {
x: x; [@inline];
y: string
} [@@deriving yaml]
That would be able to parse:
t:
- a: 1
- b: 2
- y: foo
(or whatever yaml is for a single record ;p)
into:
{ x = { a=1; b=2}; y="foo" }
Right now it looks like the serializers and deserializers are defined as Nonrecursive
:
ppx_deriving_yaml/src/ppx_deriving_yaml.ml
Line 178 in 0602bc4
This means that a type like the following won't work:
type t = {
name : string;
children : t list
}
[@@deriving yaml]
The compiler produces this error:
Unbound value to_yaml
(To be clear, I'm not suggesting that simply making the functions recursive will fix this problem, but it's at least part of why this doesn't work. Maybe setting Nonrecursive
if the type is specifically nonrec
would fix it?)
Analogously to ppx_deriving_yojson, such attributes would be convenient for overriding the implementation for a specific field without having to extract separate type declarations to do so.
Lots of derivers have a default
attribute. These let you specify that in the absence of the key
(the key in the example below is name
) then the default
value shall be what is supplied in the attribute.
type t = {
name : string; [@default "Alice"]
}[@@deriving yaml]
ppx_deriving_yaml
already has a very simplistic version of this for 'a option
types where in their absence None
is supplied (rather than failing to parse some yaml without that key). A good place to start (thanks @pitag-ha!) would be to how https://github.com/janestreet/ppx_yojson_conv/blob/master/src/ppx_yojson_conv.ml does it using Ppxlib.Attributes
https://ocaml-ppx.github.io/ppxlib/ppxlib/Ppxlib/Attribute/index.html
Consider the yaml:
places:
- name: Belfast
continent: Europe
And the OCaml type that tries to describe it:
type place = {
name : string;
continent : string;
is_city : bool;
} [@@deriving yaml]
type t = {
places : place list
} [@@deriving yaml]
With the current implementation this will fail when trying to parse Belfast's information because there is no key for is_city
.
Didn't find the function for key: is_city
(Output from this example program: https://gist.github.com/patricoferris/4f54f170a7868e27a30c9f6f87b0ec5b)
Most of our places might be cities and so it would be nice to supply a default value. In which case we'd have:
type place = {
name : string;
continent : string;
is_city : bool; [@default true]
} [@@deriving yaml]
type t = {
places : place list
} [@@deriving yaml]
And the program above (with this change) would successfully parse our list of places :)
I would like to use the ppx to partially define the type, so as to only fetch the needed fields.
For example, using a type:
type t = {
type_ : string option; [@key "type"]
}
And a yaml:
name: vaudit
type: "task"
It fails with an error namevaudit
Would it be possible to support parsing while skipping any unknown elements in the yaml ?
The [@default]
attribute added in #31 is considered in of_yaml
, but ignored in to_yaml
. It would be nice, if to_yaml
would omit fields with their default values. That is how ppx_deriving_yojson does it at least.
In particular, this would be useful for fields with option
type and [@default None]
: of_yaml
doesn't require the field in input (making it None
) and the subsequent to_yaml
wouldn't print out the field with empty value (because it's unnecessary noise for an optional field).
I'm trying to model a YAML structure like:
name: something
jobs:
- <arbitrary name>:
standard-key1: value
standard-key2: value
- <arbitrary name>:
standard-key1: value
standard-key2: value
I can achieve this serialized output by constructing my ocaml value directly from `O
and `A
literals.
But I would like to define a record type like:
type action = {
name: string;
jobs: (string * job) list;
}
[@@deriving yaml]
Is there a recipe for this in ppx_deriving_yaml
currently?
ocaml-yaml defines `O of (string * value) list
so I was hoping that jobs: (string * job) list
would work, but it seems that the (string * value)
tuples are serialized as a list instead of key-value object, so the yaml string comes out as:
name: something
jobs:
- - <arbitrary name>:
- standard-key1: value
standard-key2: value
- - <arbitrary name>:
- standard-key1: value
standard-key2: value
I am new to OCaml so I apologise if I am missing something obvious that I've done wrong.
Thanks for this useful library!
(I get the same problem from my record type using ppx_yojson_conv
and output as JSON too)
Currently, when your PPX encounter an error, it uses the raise_errorf
function to raise a located error.
The exception is caught by ppxlib
, which in this case:
[%%%ocaml.error]
extension node) to the last valid astThe interruption of the rewriting is quite bad for the user experience! The implication for the users are:
For instance:
type t = int -> int [@@deriving yaml]
type u = int -> int [@@deriving yaml]
type v = int [@@deriving yaml]
let _ = v_of_yaml
would generate several errors:
t
v_of_yojson
when the correct set of errors would be:
t
u
You can find more information about error reporting in PPXs in this section of the ppxlib manual.
โ Would you be willing to accept contributions to this issue? I'm considering assigning its resolution as part of an outreachy internship: see more information here.
A fatal error was encountered when loading the package into utop. Please see error message below.
utop # #require "ppx_deriving_yaml";;
Line 1, characters 0-4Parse error: illegal begin of top_phrase
Fatal error: exception Exit
Raised at file "string.ml", line 115, characters 25-34
Called from file "src/sexp.ml", line 113, characters 13-47
Thanks.
It would be nice if one could just derive to_yaml
or of_yaml
(with those corresponding deriver names). Then the yaml
deriver can just be a ppxlib deriver alias for the previous two.
For example, ppx_deriving_yojson registers three derivers: to_yojson
, of_yojson
and yojson
.
A very common pattern is to want to encode string values as a polymorphic (or not) variant with constant constructors. For example:
module Member = struct
type t = [ `Student | `Professor ][@@deriving yaml]
end
type t = { name : string; member : Member.t }[@@deriving yaml]
The standard [@@deriving yaml]
deriver has to deal with the possibility of non-constant constructors, so these constant ones are encode as Student: []
which is annoying because then we have:
let person = { name = "Alice"; member = `Student }
encoding to the following yaml:
name: Alice
member:
Student: []
What would be nice is to opt into some string only version that generates the following code:
let of_yaml = function
| `String "Student" -> Ok `Student
| `String "Professor" -> Ok `Professor
| _ -> Error (`Msg "Unknown value ")
let to_yaml = function
| `Student -> `String "Student"
| `Duplicate -> `String "Professor"
And additionally an extra little [@value string]
to override the default of using the constructor name verbatim.
Error handling in the deriver is not great which was helpfully discovered this thanks to @guptadiksha307.
The biggest improvement would be to provide better error messages when the wrong type of Yaml value is uncovered. For example this code could be a lot better and at least described what it was expecting to find https://github.com/patricoferris/ppx_deriving_yaml/blob/main/lib/value.ml#L162 !
Consider the following little program:
type place = {
name : string;
population : string;
} [@@deriving yaml]
type t = {
places : place list
} [@@deriving yaml]
let yml = {|
places:
- name: Belfast
population: 634594|}
let () =
match Yaml.of_string_exn yml |> of_yaml with
| Ok _ -> print_endline "Parsed Nicely :)"
| Error (`Msg m) -> print_endline m
In the data (the yml
string) you can the population of Belfast is given as a number 634594
rather than a string "634594"
. The type of population
in place
is a string
however so the generated of_yaml
function will fail because it will try to read a string
and instead find a number!
We might expect the error message to tell us something informative like Was expecting a string but got a <x>
, but instead we get the following useful error message!
err
If it is useful the code above was compiled with:
(executable
(name main)
(libraries yaml)
(preprocess
(pps ppx_deriving_yaml)))
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.