2016-06-02 6 views
0

Ich bin sehr neu in Rust - und dieser Code ist Teil eines künstlichen Lernprojektes. Denken Sie daran;)Wie können Backslash-Zeichen in PathBuf-Variablen beim Schreiben in eine JSON-Datei entfernt werden?

Ich habe eine Sammlung von Tupeln: &[(i32, String, String, PathBuf)], die in eine Funktion übergeben werden, die entwickelt wurde, um die Daten in eine JSON-Datei zu schreiben.

Das Problem: wenn ich konvertieren die PathBuf auf ein &str - der Pfad zu Datei geschrieben unescaped Backslash Zeichen hat, so dass die JSON ist ungültig.

Hier ist der Code:

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(()) 
} 

Und ein Beispiel für die Ausgabe:

{ 
    "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" 
    } 
} 

Der Code, der die PathBuf s für die MetadataPath s erzeugt, ist wie folgt:

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()); 
    } 

Und die count_nonempty_metadata_fields Funktion:

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)) 
} 

Wenn ich den ursprünglichen Verzeichnispfad Zeichenfolge zu ändern:

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

er den Ausgang nicht ändert, z.B.

{ 
    "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" 
    } 
} 

Aber es ist immer noch nicht richtig.

Fragen

  1. Wenn ich mit diesem Ansatz der Aufbau eines String in handgefertigten JSON-Format bleiben, wie bekomme ich den Pfad Inhalt der PathBuf s in ein Format mit entweder vorwärts - Schrägstriche oder Backslashes mit Umrandung? Fehle ich etwas in der API?
  2. Sollte ich ein Json Objekt verwenden, um die Daten zu erstellen (die wahrscheinlich zuverlässiger sein wird)? Wenn ja, wie ist der normale Weg, den Inhalt eines Json Objekts in eine Datei zu schreiben?

Antwort

1

Es ist eine gute Idee, nie jedes strukturiertes Format von Hand erzeugen, weil schließlich wird die Ausgabe fehlerhaft worden. Außerdem hat Ihre Ausgabe ein Objekt mit denselben zwei Schlüsseln. Während nicht ungültig, ist es wahrscheinlich nicht das, was Sie wollen.

In diesem Fall werden Sie schnell in Wände laufen mit dem Versuch, Zitate und Backslashes und vielleicht Apostrophen und Et-Zeichen zu entkommen. Sie müssen auch den letzten Punkt von Hand verfolgen. Lass die Bibliothek die harte Arbeit machen.

Es gibt zwei gute JSON-Bibliotheken für Rust: rustc_serialize und serde.

Schritt eins ist, einige tatsächliche Typen für Ihre Daten zu schaffen.Tupel sind großartig, aber werden Sie sich wirklich daran erinnern, dass foo.1 der Name ist ... oder war es foo.2?

Sobald Sie, dass, können Sie einfach Ausgabe der Scheibe:

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"); 
} 

Leider druckt dies die PathBuf in einem hässlichen Ausweg:

[{"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]}] 

Es ist wichtig zu wissen, dass PathBuf s nicht Saiten. Insbesondere sind sie plattformabhängige Abstraktionen. Auf Unix-ähnlichen Systemen ist der Pfad eine Sammlung von Bytes, die UTF-8 nahe beieinander liegen, und unter Windows ist es nahe bei, aber nicht UCS-2.

Sie müssen entscheiden, welche verlustreiche Transformation geeignet ist, um es für Ihren Fall in echte UTF-8 umzuwandeln. Ich verwende das in die Standardbibliothek eingebaute, to_string_lossy. Ich implementieren auch ToJson für die Art mehr Anpassung zu ermöglichen:

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"); 
} 

Beachten Sie, dass dies ermöglicht es Ihnen auch die Möglichkeit, die Schlüssel des Objekts zu umbenennen (obwohl die Namen mir sehr überflüssig erscheinen).

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

Dank für die Tiefe dieser Antwort, zusätzlich zu dem 'PathBuf' /' to_string_lossy() 'info, gibt es eine Reihe von weiteren Verbesserungen zu arbeiten. Was meinen Sie mit„Zusätzlich Ihr Ausgang ein Objekt mit den gleichen zwei Schlüsseln. Obwohl es nicht ungültig ist, ist es wahrscheinlich nicht das, was Sie wollen. "? Sie beziehen sich auf das doppelte Auftreten von 'ReviewItem'? Arbeite auch nicht viel mit 'JSON', aber wenn ich dich richtig interpretiere, kann ich eine" Eine geordnete Liste von Werten "(Array) anstatt" Eine Sammlung von Name/Wert-Paaren "ausgeben (Terminologie von http: // json .org /)? In diesem Fall brauche ich kein 'ReviewItem' ... danke! –

+1

@GavinHope * doppeltes Auftreten von 'ReviewItem' * - ja, das zweite Auftreten des Schlüssels überschreibt das erste Auftreten. Der Code, den ich gezeigt habe, gibt ein JSON-Array von Objekten aus, was auch bedeutet, dass Sie * wahrscheinlich * das Feld 'index' entfernen können (es ist implizit von der Reihenfolge im Array). – Shepmaster

+0

Ja - mit dem geordneten Array brauche ich nicht mehr 'Index', danke. –

Verwandte Themen