Ask several questions, thanks

  1. Where is the Ruby YAML.load_file(“some_file.yml”) equivalent?

  2. When parse a YAML file use YAML.parse(File.read("some_file.yml"), sometimes, need a object to present a empty YAML::Any document if the yaml document not exists instead of Nil, in Ruby, it is simple, just a empty hash {} is enough, how to present it in Crystal? what i means is, somethings like YAML::Any.new, but this not work.

EDIT: I know the answer, YAML.parse("{}"), there may also be better answers.

  1. Why Hash not #sort_by?

e.g. we want output sorted hash by it keys.

in Ruby, hash.sort_by {|k, v| k.to_s }, it not work in Crystal, we have to do: hash.to_a.sort_by {|x| x[0]}.to_h

  1. What is the different for following chop and strip usage?
     #lchop(prefix : Char | String) : String

	 Returns a new String with prefix removed from the beginning of the string.

	 #lstrip(char : Char) : String

	 Returns a new string with leading occurrences of char removed.


	 #lstrip(chars : String) : String

	 Returns a new string where leading occurrences of any char in chars are removed.
  1. Given a YAML::Any, how to convert it into a Hash(String, String)?
require "yaml"

x = {"test1" => "sleep 100", "test2" => "sleep 150"}.to_yaml

# Given yaml_any object.
yaml_any = YAML.parse(x)

# How to convert it into a Hash(String, String)?
z = yaml_any.as_h(String, String)  # this not work

Thanks

YAML.parse

It’s possible to use YAML::Any.new. If you want an empty has it would just be like YAML::Any.new Hash(YAML::Any, YAML::Any).new.

See `sort` and `sort_by` methods of Hash class · Issue #1915 · crystal-lang/crystal · GitHub.

I have to run, but after a quick look I think the gist of it is lstrip would remove all occurances, while lchop just removes a single one. E.g.

"aaabcdaaa".lstrip('a') # => bcdaaa
"aaabcdaaa".lchop('a')  # => aabcdaaa

It would just be:

require "yaml"

x = {"test1" => "sleep 100", "test2" => "sleep 150"}.to_yaml

yaml_any = YAML.parse(x)

z = yaml_any.as_h

pp z["test2"].as_s

No arguments are provided to to_h. A shortcut tho is using #[] with a string key:

z = yaml_any

pp z["test2"].as_s

Since it’ll internally assume the underlying value is a hash and do the .as_h call for you.

2 Likes

YAML.parse

.parse(data : String | IO) : Any only support data string or a IO object, so, i guess you means no equivalent for YAML.load_file in Ruby.

Yes, there’s no direct equivalent of the shortcut YAML.load_file. You can easily build it in Crystal with available components:

File.open("file.yaml") do |file|
  YAML.parse(file)
end
1 Like

Honestly, when it comes to dealing with YAML or JSON, using Type.from_yaml rather than YAML.parse is better nearly every time. Working with a YAML::Any is okay when you’re testing an idea, but it’s painful to work with for anything nontrivial. For example, if you’re doing it for config:

require “yaml”

struct Config
  include YAML::Serializable

  getter database_url : URI
  getter admin_email : String

  # This is also a good way to get that hash typecast, btw
  getter env : Hash(String, String)
end

Speaking of converting YAML::Any to Hash(String, String), if the file only contains string-to-string mappings, you can use Hash(String, String).from_yaml as a direct replacement of YAML.parse.

Thanks, i will, but for now, because some reason(convert code from Ruby to Crystal), i have to use YAML::Any for simple.

Current case is, the passed is a YAML::Any object, Hash(String, String).from_yaml only accept String.

You still need to do the type conversions, for the most part, even with YAML::Any using .as_s. Using YAML::Any spreads those type conversions out instead of keeping them together.

I was talking about using it in place of YAML.parse, so this would be further up the stack, not passing the YAML::Any to from_yaml.

1 Like

Do you consider we are accept a PR to add YAML.load_file or YAML.parse_file?

Anyway, i found it is tedious to have to use like

YAML.parse(File.read("some_file.yml")

Or

File.open("file.yaml") do |file|
  YAML.parse(file)
end

And, i create a PR for use YAML::Any instead of Any for better generated document, all spec was passed.

Yes, thanks, i see, pretty sure i will do as you devised in the future, it just a intermediate steps.

Yeah, I suppose it might be a useful shortcut. And it could also easily enhance error messages with the name of the file being loaded.
This would apply to all parsers which parse data that is regular loaded from files (JSON, INI, CSV).

1 Like

Okay, i will try do it in a new PR in recent days.

Please open an issue first to determine whether this would be accepted and discuss details.

1 Like

Hi, I don’t know if I do something wrong, maybe for current special case, i need convert a YAML::Any object to a Hash frequently, same discussed in above question #5.

Following is example:

require "yaml"

x = {"a" => 100, "b" => 200}.to_yaml

y = YAML.parse(x)
h = y.as_h

p! y         # => {"a" => 100, "b" => 200}
p! typeof(y) # => YAML::Any
p! h         # => {"a" => 100, "b" => 200}
p! typeof(h) # => Hash(YAML::Any, YAML::Any)

The issue is:

I expect to get a hash of String => Int32 back from the YAML::Any object, instead of {} of YAML::Any => YAML::Any.

The really use case is, above YAML::Any object is only one part of a big YAML document which parsed use YAML.parse, I an ensure
this object is a Hash(String, Int32) compatible object, i need to convert to hash to use some Hash methods, how can i do that?

Is there a way to convert YAML::Any back to YAML document? Or, give the underneath original document?

e.g.

y = YAML.parse(x)

Hash(String, Int32).from_yml(y.to_yaml_document)

# Or, Can we support pass Type into as_h?

y.as_h(String,Int32)

Is the structure of the YAML you need to parse known ahead of time? If so, then IMO the better way to do it would be to define some types to represent it using YAML::Serializable - Crystal 1.7.0-dev. Then can just do MyType.from_yaml x. Then you do not have to deal with YAML::Any at all, and have more flexibility as you can add additional methods etc.

Alternatively, the data is fairly simple, you could also do Hash(String, Int23).from_yaml x. But this could get messy if its more than a simple hash. Otherwise yes, I’m pretty sure you can just do y.to_yaml.

Yes, i am trying to change to this, although, I was planing going to do this later.

When use #from_yaml(yaml_file), if yaml_file not exists, get error message like this:

      Expected mapping, not YAML::Nodes::Scalar at line 1, column 1 (YAML::ParseException
         from /home/zw963/Crystal/share/crystal/src/yaml/nodes/nodes.cr:30:9 in 'raise'
         from /home/zw963/Crystal/share/crystal/src/yaml/serialization.cr:187:7 in 'initialize:__context_for_yaml_serializable:__node_for_yaml_serializable'

This is really confusing, i thought we need create a issue to discuss how to fix this, @Blacksmoke16 what do you think?


EDIT: Sorry, i am wrong.

Thanks, it works! i guess i do something wrong before, i missing those methods defined in Object again …

Hi, I refactor my YAML parse logic to use YAML::Seraialize instead, but i meet a new issue.

One of my config, use following struct to decode:

struct ProcessOption
    include YAML::Serializable

    property quantity : Int32?
    property restart_mode : String?
    property max_respawns : Int32?
    env : Hash(String, String)?

    def initialize
    end
  end

i need merge two config of type ProcessOption.

po = ProcessOption.from_yaml("yaml1") || ProcessOption.new
po_local = ProcessOption.from_yaml("yaml2") || ProcessOption.new

result = po.merge(po_local)

How to do that?

Thanks

As describe by my previous reply, How to handle merge on two same type struct object?

Thanks

@zw963 You would just need to implement that yourself. E.g. something like:

struct ProcessOption
  def merge(other : self)
    # Determine if you should use @quantity or other.quantity
  end
end

This would ofc be easier with a class as you could just mutate self while in the struct case, would prob have to return a new instance with the merged values.

EDIT: Or option 2 would be merge the YAML before deserializing it somehow.

1 Like