如何在写入JSON文件时转义PathBuf变量中的反斜杠字符?

时间:2016-06-02 13:01:28

标签: json rust

我对Rust很新 - 这段代码是人工学习项目的一部分。记住这一点;)

我有一组元组:&[(i32, String, String, PathBuf)],它们被传递到一个用于将数据写入JSON文件的函数中。

问题:当我将PathBuf转换为&str时 - 写入文件的路径具有未转义的反斜杠字符,因此JSON无效。

以下是代码:

use std::io;
use std::io::prelude::*;
use std::fs::File;
use std::path::PathBuf;

pub fn write_review_queue(ordered_review_queue: &[(i32, String, String, PathBuf)]) -> io::Result<()> {
    let output_file = "C:\\Dev\\Temp\\ReviewQueue\\review_queue.json";
    let mut buffer = try!(File::create(output_file));
    try!(buffer.write("{".to_string().as_bytes()));

    let mut is_first_item = true;
    for review_item in ordered_review_queue {
        if !is_first_item {
            try!(buffer.write(",".to_string().as_bytes()));
        }
        is_first_item = false;

        let json_string = "\"ReviewItem\": ".to_string() +
                          "{\"Index\": " + &review_item.0.to_string() + 
                          ", \"ReviewItemName\": \"" + &review_item.1 +
                          "\", \"ReviewItemPath\": \"" + &review_item.2 +
                          "\", \"MetadataPath\": \"" +  review_item.3.to_str().unwrap() +
                          "\"}";

        try!(buffer.write(json_string.as_bytes()));
    }


    try!(buffer.write("}".to_string().as_bytes()));
    Ok(())
}

输出的一个例子:

{
    "ReviewItem": {
        "Index": 1,
        "ReviewItemName": "Crying Cat",
        "ReviewItemPath": "C:/Temp",
        "MetadataPath": "C:\Dev\Temp\ReviewQueue\Metadata\cryingcat.json"
    },
    "ReviewItem": {
        "Index": 2,
        "ReviewItemName": "Rusty Rat",
        "ReviewItemPath": "C:/Temp",
        "MetadataPath": "C:\Dev\Temp\ReviewQueue\Metadata\rustyrat.json"
    }
}

PathBuf生成MetadataPath s的代码是这样的:

let metadata_files = metadata_read::read_filenames_from_dir("C:\\Dev\\Temp\\ReviewQueue\\Metadata");
    if !metadata_files.is_ok() {
        println!("reading metadata filenames failed");
        return;
    }

    let mut metadata_counts = Vec::new();
    for file in metadata_files.unwrap() {
        let metadata_field_count = metadata_read::count_nonempty_metadata_fields(&file, &keys);
        metadata_counts.push(metadata_field_count.unwrap());
    }

count_nonempty_metadata_fields函数:

pub fn count_nonempty_metadata_fields(file_path: &PathBuf, metadata_keys: &[String]) -> Result<(i32, String, String, PathBuf), io::Error>
{
    // a bunch of code here...

    let path = file_path.to_path_buf();
    Ok((key_count, review_item_name, review_item_path, path))
}

如果我将原始目录路径字符串更改为:

let metadata_files = metadata_read::read_filenames_from_dir("C:/Dev/Temp/ReviewQueue/Metadata");

确实改变了输出,例如

{
    "ReviewItem": {
        "Index": 1,
        "ReviewItemName": "Crying Cat",
        "ReviewItemPath": "C:/Temp",
        "MetadataPath": "C:/Dev/Temp/ReviewQueue/Metadata\cryingcat.json"
    },
    "ReviewItem": {
        "Index": 2,
        "ReviewItemName": "Rusty Rat",
        "ReviewItemPath": "C:/Temp",
        "MetadataPath": "C:/Dev/Temp/ReviewQueue/Metadata\rustyrat.json"
    }
}

但它仍然不对。

问题

  1. 如果我坚持使用手工制作的 JSON格式构建String的方法,如何将PathBuf的路径内容转换为格式有正斜杠还是逃逸反斜杠?我错过了API中的内容吗?
  2. 我是否应该使用Json对象来构建数据(可能更可靠)?如果是这样,将Json对象的内容写入文件的正常方法是什么?

1 个答案:

答案 0 :(得分:1)

从不手动生成任何结构化格式是一个好主意,因为最终输出会变得格格不入。此外,您的输出具有一个具有相同两个键的对象。虽然不是无效,但它可能不是你想要的。

在这种情况下,您将很快遇到试图逃避引号和反斜杠以及撇号和&符号的墙。您还必须手动跟踪最后一项。让图书馆努力工作。

Rust有两个很好的JSON库:rustc_serializeserde

第一步是为您的数据创建一些实际类型。元组很棒,但你真的记得foo.1是名字......还是foo.2

完成后,您只需输出切片:

extern crate rustc_serialize;

use rustc_serialize::json;

use std::io;
use std::io::prelude::*;
use std::fs::File;
use std::path::PathBuf;

#[derive(RustcEncodable)]
struct Item {
    index: i32,
    name: String,
    path: String,
    metadata_path: PathBuf,
}

fn write_review_queue(ordered_review_queue: &[Item]) -> io::Result<()> {
    let mut buffer = try!(File::create("/tmp/output"));

    write!(buffer, "{}", json::as_json(&ordered_review_queue))
}

fn main() {
    let a = [Item { index: 0, name: "He\"llo".into(), path: "Good\\bye".into(), metadata_path: PathBuf::from(r#"C:\path\with'n\special"\chars"#)}];
    write_review_queue(&a).expect("Failed");
}

不幸的是,这会以丑陋的方式打印出PathBuf

[{"index":0,"name":"He\"llo","path":"Good\\bye","metadata_path":[67,58,92,112,97,116,104,92,119,105,116,104,39,110,92,115,112,101,99,105,97,108,34,92,99,104,97,114,115]}]

了解PathBuf 字符串非常重要。具体来说,它们是与平台相关的抽象。在类Unix系统上,路径是接近但不是UTF-8的字节集合,在Windows上它是接近但不是UCS-2。

您必须决定哪种有损变换适合您的情况将其转换为真正的UTF-8。我将使用内置于标准库的to_string_lossy。我还为类型实现ToJson以允许更多自定义:

extern crate rustc_serialize;

use rustc_serialize::json::{self, ToJson, Json};

use std::io;
use std::io::prelude::*;
use std::fs::File;
use std::path::PathBuf;
use std::collections::BTreeMap;

struct Item {
    index: i32,
    name: String,
    path: String,
    metadata_path: PathBuf,
}

impl ToJson for Item {
    fn to_json(&self) -> Json {
        let mut obj = BTreeMap::new();
        obj.insert("Index".to_string(), self.index.to_json());
        obj.insert("ReviewItemName".to_string(), self.name.to_json());
        obj.insert("ReviewItemPath".to_string(), self.path.to_json());
        obj.insert("MetadataPath".to_string(), self.metadata_path.to_string_lossy().to_json());
        obj.to_json()
    }
}

fn write_review_queue(ordered_review_queue: &[Item]) -> io::Result<()> {
    let mut buffer = try!(File::create("/tmp/output"));

    write!(buffer, "{}", json::as_json(&ordered_review_queue.to_json()))
}

fn main() {
    let a = [Item { index: 0, name: "He\"llo".into(), path: "Good\\bye".into(), metadata_path: PathBuf::from(r#"C:\path\with'n\special"\chars"#)}];
    write_review_queue(&a).expect("Failed");
}

请注意,这也让您有机会重命名对象的键(尽管这些名称对我来说似乎很多)。

[{"Index":0,"MetadataPath":"C:\\path\\with'n\\special\"\\chars","ReviewItemName":"He\"llo","ReviewItemPath":"Good\\bye"}]