Weird Lines iterator capture

I am trying to iterate through a BufReader with Lines to capture certain data to a struct. It skips through the file until a certain String is found, then loops through the data to capture three String fields (nomen, id, data_type) to a struct if a line starts with a certain String. Then, the struct is pushed to a vec. It finally breaks out of the loop when either certain conditions are met.

I cannot figure out why I am having two weird issues:

  1. The loop captures the first iteration perfectly. After the second iteration, it only captures a portion of the "nomen" field. It drops everything after String.starts_with(). The other fields capture correctly.

  2. After all the fields are captured to the struct, it test the break out condition by a read line.next(). It then matches against Option. If it is None, it breaks out of the loop. If the Some matches to an Ok(String) and the String.starts_with(), it supposed to break out without capturing or pushing to the vec. But, the condition is met and it still reads the incorrect data and pushes.

Here's the code:

pub fn extract_triggers(self) -> Vec<GameObject> {
            let path = PathBuf::from(&self.filename);
            let file = File::open(path).unwrap();
            let mut lines = BufReader::new(file).lines();
            let mut trigger_vec = Vec::<GameObject>::new();

            //Move the CMF file pointer until TRIGGERS array & loop through
            lines.find(|trigger| trigger.as_ref().unwrap() == "\t\"triggers\": [");

            loop {
                // capture element ID
                let mut nomen = {
                    match lines.find(|has_name| {
                        has_name.as_ref().unwrap().starts_with("\t\t\t\t\"name\": ")
                    }) {
                        Some(nomen) => match nomen {
                            Ok(nomen) => {
                                println!("Found {}", nomen);
                                nomen
                            }
                            Err(e) => e.to_string(),
                        },
                        None => "missing data".to_string(),
                    }
                };

                let mut id = {
                    match lines
                        .find(|has_id| has_id.as_ref().unwrap().starts_with("\t\t\t\"id\": "))
                    {
                        Some(id) => match id {
                            Ok(id) => {
                                println!("Found {}", id);
                                id
                            }
                            Err(e) => e.to_string(),
                        },
                        None => "missing data".to_string(),
                    }
                };

                let mut data_type = {
                    match lines.find(|has_type| {
                        has_type
                            .as_ref()
                            .unwrap()
                            .starts_with("\t\t\t\"dataType\": ")
                    }) {
                        Some(data_type) => match data_type {
                            Ok(data_type) => data_type,
                            Err(e) => e.to_string(),
                        },
                        None => "missing data".to_string(),
                    }
                };

                trigger_vec.push(GameObject {
                    id,
                    nomen,
                    data_type,
                });

                match lines.next() {
                    None => break,
                    Some(line_data) => match line_data {
                        Err(err) => continue,
                        Ok(line) => {
                            if line.starts_with("\t]") {
                                break;
                            } else if line.starts_with("\t\t}") {
                                continue;
                            }
                        }
                    },
                }
            }

            println!("Trigger = {:#?}", trigger_vec);

            trigger_vec
        }

And, here's a sample of the results:

Trigger = [
    GameObject {
        id: "\t\t\t\"id\": 6,",
        nomen: "\t\t\t\t\"name\": \"L Eng Sel Mach CHA\"",
        data_type: "\t\t\t\"dataType\": \"BOOL_TRGR_DATA_TYPE_UINT32\",",
    },
    GameObject {
        id: "\t\t\t\"id\": 7,",
        nomen: "\t\t\t\t\"name\": \"\"",
        data_type: "\t\t\t\"dataType\": \"BOOL_TRGR_DATA_TYPE_UINT32\",",
    },
    GameObject {
        id: "\t\t\t\"id\": 8,",
        nomen: "\t\t\t\t\"name\": \"\"",
        data_type: "\t\t\t\"dataType\": \"BOOL_TRGR_DATA_TYPE_UINT32\",",
    },

...

GameObject {
        id: "\t\t\t\"id\": 0,",
        nomen: "\t\t\t\t\"name\": \"\"",
        data_type: "missing data",
    },
]

Your code is finding a line that has an empty string for the "name" field. The line it is reading looks like this:

        "name": ""

When you print it out with debug formatting, the tab and quote characters are escaped with backslashes, so it looks like this:

\t\t\t\t\"name\": \"\"

Also, your code seems to assume that the "name" field comes before the "id" and "data_type" fields, but based on the formatting I'm guessing it actually comes last in your input file. This might cause you to skip ahead when finding the next "name" field, causing it to be associated with the wrong "id" and "data_type".

I recommend using a parsing library like serde_json rather than trying to parse the file using this sort of pattern-matching. Using a good library will let you parse more reliably and with much less code.

1 Like

Thanks for the recommendation....totally didn't think to use serde!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.