15 releases

new 0.6.0 Mar 25, 2025
0.5.0 Jan 2, 2024
0.4.0 Jul 25, 2023
0.3.0 Sep 6, 2022
0.0.2 Jan 27, 2019

#145 in Database interfaces

Download history 4383/week @ 2024-12-08 3416/week @ 2024-12-15 2144/week @ 2024-12-22 1827/week @ 2024-12-29 2992/week @ 2025-01-05 3197/week @ 2025-01-12 3969/week @ 2025-01-19 4669/week @ 2025-01-26 5098/week @ 2025-02-02 4096/week @ 2025-02-09 4917/week @ 2025-02-16 4163/week @ 2025-02-23 3939/week @ 2025-03-02 4621/week @ 2025-03-09 3781/week @ 2025-03-16 3418/week @ 2025-03-23

15,890 downloads per month
Used in 22 crates (10 directly)

MIT license

220KB
5K SLoC

dbase-rs

Rust library to read and write .dbf (dBase / FoxPro) files.

Most of the dBase III and FoxPro types can be read and written, with the exception of the Memo which can only be read (writing will come in a later release).

If dbase-rs fails to read or write or does something incorrectly, don't hesitate to open an issue.


lib.rs:

dbase is rust library meant to read and write dBase / FoxPro files.

Theses files are nowadays generally found in association with shapefiles.

Reading

The Reader is the struct that you'll need to use in order to read the content of a dBase file.

Once you have access to the records, you will have to match against the real FieldValue

Examples

use dbase::FieldValue;
let records = dbase::read("tests/data/line.dbf")?;
for record in records {
    for (name, value) in record {
        println!("{} -> {:?}", name, value);
        match value {
            FieldValue::Character(Some(string)) => println!("Got string: {}", string),
            FieldValue::Numeric(value) => println!("Got numeric value of  {:?}", value),
            _ => {}
        }
    }
}

You can also create a Reader and iterate over the records.

let mut reader = dbase::Reader::from_path("tests/data/line.dbf")?;
for record_result in reader.iter_records() {
    let record = record_result?;
    for (name, value) in record {
        println!("name: {}, value: {:?}", name, value);
    }
}

Other Codepages / Encodings

As baseline, dbase-rs only suppors Utf8 and Utf8-lossy encodings, meaning only files using strings in ASCII encoding will properly be deccoded. However, two optional features exist to work with non-ASCII encodings:

  • yore: uses the yore crate supports most code pages
  • encoding_rs: uses the encoding_rs crate, supports notably the GBK encoding

If both feature are activated, "yore" takes the priority.

To force the use of a particular encoding:

use yore::code_pages::CP850;

let mut reader = dbase::Reader::from_path_with_encoding("tests/data/cp850.dbf", CP850)?;
let records = reader.read()?;

assert_eq!(records[0].get("TEXT"), Some(&dbase::FieldValue::Character(Some("Äöü!§$%&/".to_string()))));


The functions that do not take an encoding as parameter, use UnicodeLossy by default, they try to read all data as Unicode and replace unrepresentable characters with the unicode replacement character. Alternatively Unicode is available, to return an [Err] when data can't be represented as Unicode.

Deserialisation

If you know what kind of data to expect from a particular file you can use implement the ReadbableRecord trait to "deserialize" the record into your custom struct:

 use std::io::{Read, Seek};
 use dbase::Encoding;

 struct StationRecord {
     name: String,
     marker_col: String,
     marker_sym: String,
     line: String,
 }

 impl dbase::ReadableRecord for StationRecord {
     fn read_using<R1, R2>(field_iterator: &mut dbase::FieldIterator<R1, R2>) -> Result<Self, dbase::FieldIOError>
          where R1: Read + Seek,
                R2: Read + Seek,
    {
        use dbase::Encoding;
        Ok(Self {
            name: field_iterator.read_next_field_as()?.value,
            marker_col: field_iterator.read_next_field_as()?.value,
            marker_sym: field_iterator.read_next_field_as()?.value,
            line: field_iterator.read_next_field_as()?.value,
        })
     }
 }
 # fn main() -> Result<(), dbase::Error> {
 let mut reader = dbase::Reader::from_path("tests/data/stations.dbf")?;
 let stations = reader.read_as::<StationRecord>()?;

 assert_eq!(stations[0].name, "Van Dorn Street");
 assert_eq!(stations[0].marker_col, "#0000ff");
 assert_eq!(stations[0].marker_sym, "rail-metro");
 assert_eq!(stations[0].line, "blue");
 # Ok(())
 # }

If you use the serde optional feature and serde_derive crate you can have the ReadbableRecord impletemented for you

extern crate serde_derive;


use std::io::{Read, Seek};
use serde_derive::Deserialize;

#[derive(Deserialize)]
struct StationRecord {
    name: String,
    marker_col: String,
    marker_sym: String,
    line: String,
}

let mut reader = dbase::Reader::from_path("tests/data/stations.dbf")?;
let stations = reader.read_as::<StationRecord>()?;

assert_eq!(stations[0].name, "Van Dorn Street");
assert_eq!(stations[0].marker_col, "#0000ff");
assert_eq!(stations[0].marker_sym, "rail-metro");
assert_eq!(stations[0].line, "blue");

Writing

In order to get a TableWriter you will need to build it using its TableWriterBuilder to specify the fields that constitute a record.

As for reading, you can serialize structs into a dBase file, given that they match the declared fields in when building the TableWriterBuilder by implementing the WritableRecord.

Examples

let mut reader = dbase::Reader::from_path("tests/data/stations.dbf")?;
let mut stations = reader.read()?;

let mut writer = dbase::TableWriterBuilder::from_reader(reader)
    .build_with_file_dest("stations.dbf").unwrap();

stations[0].get_mut("line").and_then(|_old| Some("Red".to_string()));
writer.write_records(&stations)?;
 use dbase::{TableWriterBuilder, FieldName, WritableRecord, FieldWriter, FieldIOError, Encoding};
 use std::convert::TryFrom;
 use std::io::{Cursor, Write};

 struct User {
     nick_name: String,
     age: f64
 }

 impl WritableRecord for User {
     fn write_using<'a, W>(&self, field_writer: &mut FieldWriter<'a, W>) -> Result<(), FieldIOError>
         where W: Write
     {
         field_writer.write_next_field_value(&self.nick_name)?;
         field_writer.write_next_field_value(&self.age)?;
         Ok(())
     }
 }

 let mut writer = TableWriterBuilder::new()
     .add_character_field(FieldName::try_from("Nick Name").unwrap(), 50)
     .add_numeric_field(FieldName::try_from("Age").unwrap(), 20, 10)
     .build_with_dest(Cursor::new(Vec::<u8>::new()));


 let records = User{
     nick_name: "Yoshi".to_string(),
     age: 32.0,
 };

 writer.write_record(&records).unwrap();

If you use the serde optional feature and serde_derive crate you can have the WritableRecord implemented for you.

extern crate serde_derive;

use serde_derive::Serialize;

use dbase::{TableWriterBuilder, FieldName, WritableRecord, FieldWriter};
use std::convert::TryFrom;
use std::io::{Cursor, Write};

#[derive(Serialize)]
struct User {
    nick_name: String,
    age: f64
}

let writer = TableWriterBuilder::new()
    .add_character_field(FieldName::try_from("Nick Name").unwrap(), 50)
    .add_numeric_field(FieldName::try_from("Age").unwrap(), 20, 10)
    .build_with_dest(Cursor::new(Vec::<u8>::new()));


let records = vec![User{
    nick_name: "Yoshi".to_string(),
    age: 32.0,
}];

    writer.write_records(&records);

File

This crate also has a third option to handle dbase files, the [File] struct.

This struct allows to read/write an existing or new file without having to fully read it first.

Dependencies

~0.8–22MB
~348K SLoC