如何在Clojure中按给定日期过滤JSON数据?

时间:2018-04-15 13:50:00

标签: clojure

我有很多JSON对象,我试图按日期过滤这些对象。使用Cheshire.core从几个JSON文件中解析这些对象,这意味着JSON对象位于集合中。日期的格式为“YYYY-MM-DD”(例如2015-01-10)。我试过使用过滤器并包含?这样做的功能,但到目前为止我没有运气。如何按我选择的日期过滤这些JSON对象?

目前的Clojure代码:

(def filter-by-date?
    (fn [orders-data date-chosen]
      (contains? (get (get orders-data :date) :date) date-chosen)))


(prn (filter (filter-by-date? orders-data "2017-12-25")))

示例JSON对象:

{
    "id":"05d8d404-b3f6-46d1-a0f9-dbdab7e0261f",
    "date":{
        "date":"2015-01-10T19:11:41.000Z"
    },
    "total":{
        "GBP":57.45
    }
}
用Cheshire解析后的

[({:id "05d8d404-b3f6-46d1-a0f9-dbdab7e0261f", 
:date {:date "2015-01-10T19:11:41.000Z"}, 
:total {:GBP 57.45}}) ({:id "325bd04-b3f6-46d1-a0f9-dbdab7e0261f", 
:date {:date "2015-02-23T10:15:14.000Z"}, 
:total {:GBP 32.90}})]

4 个答案:

答案 0 :(得分:1)

首先,我假设您已经将JSON首先解析为以下内容:

(def parsed-JSON {:id "05d8d404-b3f6-46d1-a0f9-dbdab7e0261f",
                  :date {:date "2015-01-10T19:11:41.000Z"},
                  :total {:GBP 57.45}})

主要问题是存储在JSON中的日期包含时间信息,因此您无法使用相等性直接检查它。

您可以使用clojure.string/starts-with?来检查前缀。我在这里使用s/作为clojure.string的别名:

(defn filter-by-date [date jsons]
  (filter #(s/starts-with? (get-in % [:date :date]) date)
          jsons))

你很亲密,但我做了一些改变:

  • 您不能像这样使用contains?。来自contains?Returns true if key is present in the given collection, otherwise returns false的文档。它不能用于检查子串;它曾用于测试集合中是否存在密钥。

  • 使用-in后缀版本访问嵌套结构,而不是使用多个调用。我在这里使用(get-in ...)而不是(get (get ...))

  • 您正在使用(def ... (fn [])),这使得事情变得比他们需要的更复杂。 This is essentially what defn does,虽然defn也增加了一些内容。

要解决新信息,您可以先flatten包含JSON的嵌套序列:

(->> nested-json-colls ; The data at the bottom of the question
     (flatten)
     (filter-by-date "2015-01-10")) 

答案 1 :(得分:1)

#!/usr/bin/env boot

(defn deps [new-deps]
  (merge-env! :dependencies new-deps))

(deps '[[org.clojure/clojure "1.9.0"]
        [cheshire "5.8.0"]])

(require '[cheshire.core :as json]
         '[clojure.string :as str])

(def orders-data-str
"[{
    \"id\":\"987654\",
    \"date\":{
        \"date\":\"2015-01-10T19:11:41.000Z\"
    },
    \"total\":{
        \"GBP\":57.45
    }
},
{
    \"id\":\"123456\",
    \"date\":{
        \"date\":\"2016-01-10T19:11:41.000Z\"
    },
    \"total\":{
        \"GBP\":23.15
    }
}]")

(def orders (json/parse-string orders-data-str true))

(def ret (filter #(clojure.string/includes? (get-in % [:date :date]) "2015-01-") orders))
(println ret)  ; ({:id 987654, :date {:date 2015-01-10T19:11:41.000Z}, :total {:GBP 57.45}})

您可以使用任何DateTime库(如joda-time)将日期字符串转换为Date对象,然后根据需要进行适当的过滤。

答案 2 :(得分:0)

clj-time具有解析字符串和比较日期时间对象的功能。所以你可以这样做:

(ns filter-by-time-example
  (:require [clj-time.coerce :as tc]
            [clj-time.core :as t]))

(def objs [{"id" nil
            "date" {"date" "2015-01-12T19:11:41.000Z"}
            "total" nil}
           {"id" "05d8d404-b3f6-46d1-a0f9-dbdab7e0261f"
            "date" {"date" "2015-01-10T19:11:41.000Z"}
            "total" {"GBP" :57.45}}
           {"id" nil
            "date" {"date" "2015-01-11T19:11:41.000Z"}
            "total" nil}])

(defn filter-by-day
  [objs y m d]
  (let [start (t/date-time y m d)
        end (t/plus start (t/days 1))]
    (filter #(->> (get-in % ["date" "date"])
                  tc/from-string
                  (t/within? start end)) objs)))

(clojure.pprint/pprint (filter-by-day objs 2015 1 10)) ;; Returns second obj

如果您要反复执行此操作(例如多天),您可以使用

将集合中的所有日期解析为日期时间对象
(map #(update-in % ["date" "date"] tc/from-string) objs)

然后只使用该集合以避免重复解析步骤。

答案 3 :(得分:0)

(ns filter-by-time-example
  (:require [clj-time.format :as f]
            [clj-time.core :as t]
            [cheshire.core :as cheshire]))

(->> json-coll
     (map (fn [json] (cheshire/parse-string json true)))
     (map (fn [record] (assoc record :dt-date (f/format (get-in record [:date :date])))))
     (filter (fn [record] (t/after? (tf/format "2017-12-25") (:dt-date record))))
     (map (fn [record] (dissoc record :dt-date))))

也许是这样的?您可能需要更改用例的过滤器,但由于:dt-time现在是jodo.DateTime,您可以利用所有clj-time谓词。