Search code examples
rustiteratortraitsserde-json

How to implement Iterator in Rust for third party library


Im trying to make the following work

use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let json = reqwest::get("https://www.bitmex.com/api/v1/instrument/indices")
        .await?
        .json::<serde_json::Value>()
        .await?;

    for key in json {
        println!("{:?}", key["symbol"]);
    }

    Ok(())
}

which results in the following

--> src/main.rs:8:16
  |
8 |     for key in json {
  |                ^^^^ `Value` is not an iterator
  |
  = help: the trait `Iterator` is not implemented for `Value`
  = note: required because of the requirements on the impl of `IntoIterator` for `Value`

I tried to implement it in the local source file serde_json-1.0.79/src/value/mod.rs as


impl IntoIterator for Value {
    type Item = Value;
    type IntoIter = ValueIterator;
    fn into_iter(self) -> Self::IntoIter {
        ValueIterator {
            slice: self.as_array().unwrap().iter(),
        }
    }
}

struct ValueIterator {
    slice: std::slice::Iter<Value>,
}

impl Iterator for ValueIterator {
    type Item = Value;

    fn next(&mut self) -> Option<Self::Item> {
        self.slice.next()
    }
}

But the error remains, what can i change in the iterator implementation to make the code work as is?


Solution

  • You can't. You need to wrap it a new type defined within your crate first before you implement a trait for it. The easy way around this is to make a new trait you can implement on the foreign types which does what you want. Alternatively you can wrap it in a struct to avoid the limitations entirely.

    pub trait ValueExt {
        type ArrayIter: IntoIter<Item=Foo>;
        fn json_array_iter(&self) -> Self::ArrayIter;
    }
    
    impl ValueExt for Value {
        type ArrayIter = ValueIterator;
    
        fn json_array_iter(&self) -> Self::ArrayIter {
            ValueIterator {
                slice: self.as_array().unwrap().iter(),
            }
        }
    }
    
    // Now you can call your trait while iterating
    for item in value.json_array_iter() {
        // etc.
    }
    

    That being said, the solution for serde_json is to just expand the Value first to check if it is an array before iterating over the elements.

    if let Value::Array(elements) = value_to_iterate {
        for item in elements {
            // etc.
        }
    }
    

    Edit:

    For fun, here is an iterator that will go through key value pairs of a JSON value. It will work on strings (index, UTF-8 character), arrays (index, value), and maps (key, value).

    for (key, value) in json_object.items().unwrap() {
        println!("Key: {:?}, Value: {:?}", key, value);
    }
    

    Here is the actual implementation and playground link.

    use std::iter::Enumerate;
    use std::str::Chars;
    use std::slice::Iter;
    use serde_json::{json, Value};
    use serde_json::map::Iter as MapIter;
    
    pub enum KVIter<'a> {
        StrIter ( Enumerate<Chars<'a>>),
        ArrIter (Enumerate<Iter<'a, Value>>),
        MapIter (MapIter<'a>),
    }
    
    impl<'a> Iterator for KVIter<'a> {
        type Item = (Value, Value);
        
        fn next(&mut self) -> Option<Self::Item> {
            match self {
                Self::StrIter(chars) => {
                    let (idx, character) = chars.next()?;
                    Some((json!(idx), json!(character)))
                }
                Self::ArrIter(values) => {
                    let (idx, value) = values.next()?;
                    Some((json!(idx), value.clone()))
                }
                Self::MapIter(items) => {
                    let (key, value) = items.next()?;
                    Some((json!(key), value.clone()))
                }
            }
        }
    }
    
    pub trait IntoKVIter {
        fn items(&self) -> Option<KVIter>;
    }
    
    impl<'a> IntoKVIter for Value {
        fn items(&self) -> Option<KVIter> {
            Some(match self {
                Value::String(string) => KVIter::StrIter(string.chars().enumerate()),
                Value::Array(values) => KVIter::ArrIter(values.into_iter().enumerate()),
                Value::Object(map) => KVIter::MapIter(map.into_iter()),
                _ => return None,
            })
        }
    }