Unwrapping objects in a vector when deserialising json

Hi all!

I'm having difficulties getting some json to play nicely.

For an MWE I have an input field that can take a few different types, but only one of them at a time. So that's simple: I can use an enum. My issue is that the source json (which I have no control over) is wrapped a few levels deep.

{    
  "inner": {    
    "name": "inner_name",    
    "data": "test"    
  }    
}

Is one possibility for input, but it could also be

{ "join: [ 
  {    
    "inner": {    
      "name": "inner_name_one",    
      "data": "test_one"    
    }    
  },
  {    
    "inner": {    
      "name": "inner_name_two",    
      "data": "test_two"    
    }    
  },
  {    
    "inner": {    
      "name": "inner_name_tree",    
      "data": "test_three"    
    }    
  }
 ]
}

What I have at the moment:

// serde, serde_json

#[derive(Debug, Deserialize)]
struct Inner {
  name: String,
  data: String
}

#[derive(Debug, Deserialize)]
struct WrappedInner {
  inner: Inner
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Input {
  Inner(Inner),
  Join(Vec<WrappedInner>),
}

What I would like:

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Input {
  Inner(Inner),
  Join(Vec<Inner>),
}

I've tried to write custom Deserialize and Visitor traits for Inner and successfully managed to get the Join case to parse to Join(Vec<Inner>), but failed to generalise this, since the Inner enum call would then fail.

Any ideas on how best to approach this conversion?

Are you sure you don't want ↓?

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Input {
  Inner(Inner),
  Join(Vec<Input>), // ← note, Vec<Input>, not Vec<Inner>!
}

I mean – perhaps the input format does support nested joins?

Interesting thought!

Although there are two issues for my complete solution.
First: Inner is the only type that Join allows, there are other types in my list that I can't allow to nest.

Second: the result is still wrapped in an additional layer of complexity: Vec<Input::Inner> and not just Vec<Inner>, so another level of matching still has to happen whenever I parse the result.

Oh, I think I got it!

How does this look?

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Input {
  Inner(Inner),
  #[serde(deserialize_with = "inner_unwrap")]
  Join(Vec<Inner>),
}

fn inner_unwrap<'de, D>(deserializer: D) -> Result<Vec<Inner>, D::Error>
where
  D: Deserializer<'de>
{
  let v: Vec<WrappedInner> = Deserialize::deserialize(deserializer)?;
  let values = v.into_iter().map(|f| f.inner).collect();
  Ok(values)
}
1 Like

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.