【问题标题】:Ruby Time#to_json as a floatRuby Time#to_json 作为浮点数
【发布时间】:2018-11-16 05:44:04
【问题描述】:

Ruby 的 json 库默认将 Time 对象转换为字符串

require 'json'
Time.at(1000).utc.to_json # => "\"1970-01-01 00:16:40 UTC\"" 

这样做的问题是我们失去了精度。我希望 to_json 生成一个浮点数。

我也知道有一些使用oj 或需要json/add/time 的解决方法,但是这两种方法都会将多余的数据添加到输出中并且不是最便携的。

一个简单的方法是猴子补丁时间,虽然我不喜欢这样做,尤其是核心类

class Time
  def to_json(*a)
    self.to_f.to_json(*a)
  end
end

有没有更好的方法?

【问题讨论】:

  • 如果你真的想要一个浮点数,那么Time#to_f 会为你返回一个浮点数。甚至 BigDecimal.new(Time.at(1000.123456).to_r,24).to_s('24F').to_json 如果 yoctosecond 精度真的很重要

标签: json ruby


【解决方案1】:

一个简单的方法是猴子补丁时间,虽然我不喜欢这样做,尤其是核心类

日期没有 JSON 格式,就 JSON 而言,它们只是字符串。大多数语言都理解ISO 8601,这就是Time#to_json 产生的。只要Time#to_json 继续生成 ISO 8601 日期时间,您就可以保持向后兼容。

require 'json'
require 'time'  # for Time#iso8601 and Time.parse

class Time
  def to_json
    return self.iso8601(6).to_json
  end
end

time = Time.at(1000.123456)
puts "Before: #{time.iso8601(6)}"

json_time = Time.at(1000.123456).to_json
puts "As JSON: #{json_time}"

# Demonstrate round tripping.
puts "Round trip: #{Time.parse(JSON.parse(json_time)).iso8601(6)}"
Before: 1969-12-31T16:16:40.123456-08:00
As JSON: "1969-12-31T16:16:40.123456-08:00"
Round trip: 1969-12-31T16:16:40.123456-08:00

如果您对全局猴子补丁不满意,您可以通过实现around 单独进行猴子补丁。

class Time
  require 'time'
  require 'json'

  def precise_to_json(*args)
    return iso8601(6).to_json(*args)
  end

  alias_method :original_to_json, :to_json
end

module PreciseJson
  def self.around
    # Swap in our precise_to_json as Time#to_json
    Time.class_eval {
      alias_method :to_json, :precise_to_json
    }

    # This block will use Time#precise_to_json as Time#to_json
    yield

  # Always put the original Time#to_json back.
  ensure
    Time.class_eval {
      alias_method :to_json, :original_to_json
    }
  end
end

obj = { 
  time: Time.at(1000.123456),
  string: "Basset Hounds Got Long Ears"
}

puts "Before: #{obj.to_json}"

PreciseJson.around {
  puts "Around: #{obj.to_json}"
}

puts "After: #{obj.to_json}"

begin
  PreciseJson.around {
    raise Exception
  }
rescue Exception
end

puts "After exception: #{obj.to_json}"
Before: {"time":"1969-12-31 16:16:40 -0800","string":"Basset Hounds Got Long Ears"}
Around: {"time":"1969-12-31T16:16:40.123456-08:00","string":"Basset Hounds Got Long Ears"}
After: {"time":"1969-12-31 16:16:40 -0800","string":"Basset Hounds Got Long Ears"}
After exception: {"time":"1969-12-31 16:16:40 -0800","string":"Basset Hounds Got Long Ears"}

【讨论】:

  • 谢谢。很好,iso8601 对我来说可能是一个好方法。不过,这仍在修改 Time 类。有什么避免这种情况的想法吗?
  • @jessebs 你需要猴子补丁才能在嵌入其他对象的时间对象上工作。你可以使用refinements,但它们在范围内是词法的,所以don't recurse into obj.to_json。另一种方法是实现around 以仅针对某个代码块进行猴子补丁。我已经更新了答案来演示。
  • @jessebs 在这种情况下这真的不是问题。 Time 类没有特定的to_json 方法,因此它回退到Object#to_json,这只是to_s 表示,如Here 所示,因此修补它确实没有具体影响,尽管签名可能应该是to_json(*) 与所有其他实现保持一致。但是你可以使用Time.at(1000.123456).strftime("%Y-%m-%dT%H:%M:%S.%6N%z").to_json
  • 非常有趣。您可能希望使DateTime#iso8601 的参数等于9,以便在不检查有效数字的情况下获得纳秒数的完整精度。当我执行Time.now.nsec 时,我得到7 有效数字(Windows 10 上的 Ubuntu 上的 Bash)。您可以将return self.iso8601(6).to_json 缩短为iso8601(6).to_json。 (我相信你知道这一点。我猜你现在可能大部分时间都在使用 Ruby 以外的语言。)
  • @CarySwoveland 感谢您的注释。我现在大部分时间都花在 Ruby 上。我在很大程度上将Time#to_json 的特定细节留给了 OP(微秒级似乎是安全的),并专注于使其在没有全局猴子补丁的情况下透明和递归地工作。我偶尔会被foo 不等于self.foo 所困扰,可能是变量/方法的歧义,所以有时我很保守。我对around 的实现非常满意,可能会对其进行推广并将其变成一个库。
【解决方案2】:

您可以保存自纪元以来的纳秒数。

require 'time'
require 'json'

t = Time.now
  #=> 2018-11-13 13:23:32 -0800
json = [t.to_i, t.nsec].to_json
  #=> "[1542144212,883611300]"

secs, nsecs = JSON.parse(json)
  #=> [1542144212, 883611300]
r = secs + 10**-9 * nsecs
  #=> (15421442128836113/10000000)
tt = Time.at r
  #=> 2018-11-13 13:23:32 -0800

t == tt
  #=> true

注意(10**-9).class #=> Rational

【讨论】:

  • require 'json/add/time' 然后 Time.now.to_json #=> "{\"json_class\":\"Time\",\"s\":1542145917,\"n\":74844000}" 其中s 是秒,n 是纳秒
  • 它还添加了Time::json_create 所以th= JSON.parse(Time.now.to_json); t= Object.const_get(th.delete("json_class")).json_create(th)
  • 我在原始问题中提到要避免json/add/time。它使语言之间的 json 解析更加困难
  • jessebs,我错过了,所以回滚到我原来的答案。
猜你喜欢
  • 2013-09-13
  • 2011-02-14
  • 1970-01-01
  • 1970-01-01
  • 2015-07-18
  • 1970-01-01
  • 1970-01-01
  • 2013-11-22
  • 1970-01-01
相关资源
最近更新 更多