ATProto Browser

ATProto Browser

Experimental browser for the Atmosphere

Record data

{
  "uri": "at://did:plc:7mnpet2pvof2llhpcwattscf/beauty.piss.blog.entry/3llrqhi32wo2a",
  "cid": "bafyreibitpke3hug7bjjlcb5utx4k5ekvaqeexh23sdwt4agjzlw5pqsou",
  "value": {
    "tags": [
      "parsing",
      "rust",
      "dev"
    ],
    "$type": "beauty.piss.blog.entry",
    "title": "\"Filter Parsing\" JSON",
    "content": "# \"filter parsing\" JSON\n\n\"filter parsing\" is the name i use to refer to the practice of fallibly parsing some type `T` from (bytes/json/etc) to `Option<T>`, rather than `Result<T>`. sometimes, you just don't care what went wrong, and only care about the valid data.\n\nwhen there's a parsing error, instead of bubbling the error, filter parsing swallows them (though may report them to logging/tracing) and returns `None`. here's an example:\n\n## dealing with irregular upstream data\n\nwe have an upstream data source that we query with batches of keys, and the struct we usually parse each item in the response array to is like this:\n\n```rust\n#[derive(Deserialize)]\npub struct ResponseElement {\n    // other fields...\n    pub decimals: u8\n}\n```\n\nsometimes, the response includes a JSON object where `decimals` is out of range even for `u16`, which is a clearly unreasonable case - decimal places lose human meaning long before 255 of them, let alone 65,535. when we inspected the bad data, clearly other fields in the object looked whack as well, so we just want to throw these cases out.\n\ncrucially however, we don't want to throw away the other elements of the array that parse fine. if you parse a `Vec<T>` from a JSON array, using `serde_json`, and a single array member fails to parse, the whole parse throws an error.\n\nso, we've been using the following lil' tool i made for this pattern in production at work, for at least a month.\n\n## `FilterParsedJsonVec<T>`\n\nthis struct wraps up the JSON parsing pattern, and makes it easy to get a `Vec<T>` of all the parseable values.\n\n```rust\nuse std::collections::VecDeque;\nuse serde::{Deserialize, de::DeserializeOwned};\n\n#[derive(Debug, Deserialize)]\n#[serde(transparent)]\npub struct FilterParsedJsonVec<T: DeserializeOwned>(\n    pub VecDeque<serde_json::Value>,\n    #[serde(skip)] std::marker::PhantomData<T>,\n);\n```\nit accomplishes that via a custom iterator that yields `Option<T>`:\n```rust\nimpl<T: DeserializeOwned> Iterator for FilterParsedJsonVec<T> {\n    type Item = Option<T>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.0.pop_front().map(|v| match serde_json::from_value(v) {\n            Ok(val) => Some(val),\n            Err(_) => None,\n        })\n    }\n}\n```\n\nand this important bit of sweetener:\n```rust\nimpl<T: DeserializeOwned> From<FilterParsedJsonVec<T>> for Vec<T> {\n    fn from(val: FilterParsedJsonVec<T>) -> Self {\n        val.into_iter().flatten().collect()\n    }\n}\n```\n### using it\nthe idea is, you parse as `FilterParsedJsonVec<T>` first, because `serde_json` can't fail to parse a `serde_json::Value` for any valid JSON value. and then the iterator over it does the actual parsing to your `T`. \n\nan example using integers for simplicity (in practice, this is meant to be used with more complex structs):\n```rust\nlet json_arr: FilterParsedJsonVec<u16> = serde_json::from_str(\"[{}, 2, -1, 4]\");\nlet valid_items: Vec<u16> = json_arr.into();\nassert_eq!(valid_items, [2, 4]); \n```\n\n### that's the whole thing\n\nit's really simple and it made dealing with weird parsing failures in incoming JSON arrays a breeze. \nbeing able to convert it to `Vec<T>` with just an `.into()` is a good devex in my experience.\n\nthe code for `FilterParsedJsonVec<T>` is [available on my github](https://github.com/stella3d/filter-parsed-json).\n\nit's not packaged as a crate yet, maybe if i mature it a bit i will do that.\n",
    "createdAt": "2025-04-03T21:43:30.765Z"
  }
}