Skip to main content

subtr_actor/collector/stats/
playback.rs

1use boxcars::{Ps4Id, PsyNetId, RemoteId, SwitchId};
2use serde::de::DeserializeOwned;
3use serde::Serialize;
4use serde_json::{Map, Value};
5
6use crate::*;
7
8use super::types::serialize_to_json_value;
9
10#[derive(Debug, Clone, PartialEq, Serialize)]
11pub struct CapturedStatsFrame<Modules> {
12    pub frame_number: usize,
13    pub time: f32,
14    pub dt: f32,
15    pub seconds_remaining: Option<i32>,
16    pub game_state: Option<i32>,
17    pub gameplay_phase: GameplayPhase,
18    pub is_live_play: bool,
19    pub modules: Modules,
20}
21
22pub type StatsSnapshotFrame = CapturedStatsFrame<Map<String, Value>>;
23
24#[derive(Debug, Clone, PartialEq, Serialize)]
25pub struct CapturedStatsData<Frame> {
26    pub replay_meta: ReplayMeta,
27    pub config: Map<String, Value>,
28    pub modules: Map<String, Value>,
29    pub frames: Vec<Frame>,
30}
31
32pub type StatsSnapshotData = CapturedStatsData<StatsSnapshotFrame>;
33
34impl<Modules> CapturedStatsFrame<Modules> {
35    pub fn map_modules<Mapped, F>(
36        self,
37        transform: F,
38    ) -> SubtrActorResult<CapturedStatsFrame<Mapped>>
39    where
40        F: FnOnce(Modules) -> SubtrActorResult<Mapped>,
41    {
42        Ok(CapturedStatsFrame {
43            frame_number: self.frame_number,
44            time: self.time,
45            dt: self.dt,
46            seconds_remaining: self.seconds_remaining,
47            game_state: self.game_state,
48            gameplay_phase: self.gameplay_phase,
49            is_live_play: self.is_live_play,
50            modules: transform(self.modules)?,
51        })
52    }
53}
54
55impl CapturedStatsData<StatsSnapshotFrame> {
56    pub fn into_stats_timeline(self) -> SubtrActorResult<ReplayStatsTimeline> {
57        self.to_stats_timeline()
58    }
59
60    pub fn into_stats_timeline_with_progress<F>(
61        self,
62        frame_interval: usize,
63        mut on_progress: F,
64    ) -> SubtrActorResult<ReplayStatsTimeline>
65    where
66        F: FnMut(usize, usize) -> SubtrActorResult<()>,
67    {
68        let frame_interval = frame_interval.max(1);
69        let total_frames = self.frames.len();
70        on_progress(0, total_frames)?;
71        let frames = self
72            .frames
73            .iter()
74            .enumerate()
75            .map(|(frame_index, frame)| {
76                let replay_frame = self.replay_stats_frame(frame)?;
77                let processed_frames = frame_index + 1;
78                if processed_frames == total_frames
79                    || processed_frames.is_multiple_of(frame_interval)
80                {
81                    on_progress(processed_frames, total_frames)?;
82                }
83                Ok(replay_frame)
84            })
85            .collect::<SubtrActorResult<Vec<_>>>()?;
86        self.to_replay_stats_timeline_with_frames(frames)
87    }
88
89    pub fn to_stats_timeline(&self) -> SubtrActorResult<ReplayStatsTimeline> {
90        self.to_replay_stats_timeline_with_frames(
91            self.frames
92                .iter()
93                .map(|frame| self.replay_stats_frame(frame))
94                .collect::<SubtrActorResult<Vec<_>>>()?,
95        )
96    }
97
98    pub(crate) fn into_replay_stats_timeline_with_frames(
99        self,
100        frames: Vec<ReplayStatsFrame>,
101    ) -> SubtrActorResult<ReplayStatsTimeline> {
102        self.to_replay_stats_timeline_with_frames(frames)
103    }
104
105    fn to_replay_stats_timeline_with_frames(
106        &self,
107        frames: Vec<ReplayStatsFrame>,
108    ) -> SubtrActorResult<ReplayStatsTimeline> {
109        Ok(ReplayStatsTimeline {
110            config: self.timeline_config(),
111            replay_meta: self.replay_meta.clone(),
112            events: self.timeline_event_sets_typed()?,
113            frames,
114        })
115    }
116
117    pub fn into_stats_timeline_value(self) -> SubtrActorResult<Value> {
118        self.to_stats_timeline_value()
119    }
120
121    pub fn to_stats_timeline_value(&self) -> SubtrActorResult<Value> {
122        let mut timeline = Map::new();
123        timeline.insert("config".to_owned(), self.timeline_config_value()?);
124        timeline.insert(
125            "replay_meta".to_owned(),
126            serialize_to_json_value(&self.replay_meta)?,
127        );
128        timeline.insert("events".to_owned(), self.timeline_event_sets_value());
129        timeline.insert(
130            "frames".to_owned(),
131            Value::Array(
132                self.frames
133                    .iter()
134                    .map(|frame| self.timeline_frame_value(frame))
135                    .collect::<SubtrActorResult<Vec<_>>>()?,
136            ),
137        );
138        Ok(Value::Object(timeline))
139    }
140
141    fn timeline_events(&self) -> Vec<Value> {
142        let mut events = self.module_array("core", "timeline");
143        events.extend(self.module_array("demo", "timeline"));
144        events.sort_by(|left, right| {
145            let left_time = left.get("time").and_then(Value::as_f64).unwrap_or(0.0);
146            let right_time = right.get("time").and_then(Value::as_f64).unwrap_or(0.0);
147            left_time.total_cmp(&right_time)
148        });
149        events
150    }
151
152    fn timeline_events_typed(&self) -> SubtrActorResult<Vec<TimelineEvent>> {
153        self.timeline_events()
154            .iter()
155            .map(parse_timeline_event)
156            .collect()
157    }
158
159    fn timeline_event_sets_typed(&self) -> SubtrActorResult<ReplayStatsTimelineEvents> {
160        Ok(ReplayStatsTimelineEvents {
161            timeline: self.timeline_events_typed()?,
162            backboard: self.module_player_events("backboard", "events", parse_backboard_event)?,
163            ceiling_shot: self.module_player_events(
164                "ceiling_shot",
165                "events",
166                parse_ceiling_shot_event,
167            )?,
168            double_tap: self.module_player_events(
169                "double_tap",
170                "events",
171                parse_double_tap_event,
172            )?,
173            fifty_fifty: self.module_player_events(
174                "fifty_fifty",
175                "events",
176                parse_fifty_fifty_event,
177            )?,
178            rush: self.module_typed_array("rush", "events")?,
179            speed_flip: self.module_player_events(
180                "speed_flip",
181                "events",
182                parse_speed_flip_event,
183            )?,
184            boost_pickups: self.module_player_events(
185                "boost",
186                "events",
187                parse_boost_pickup_comparison_event,
188            )?,
189        })
190    }
191
192    fn timeline_event_sets_value(&self) -> Value {
193        let mut events = Map::new();
194        events.insert("timeline".to_owned(), Value::Array(self.timeline_events()));
195        events.insert(
196            "backboard".to_owned(),
197            Value::Array(self.module_array("backboard", "events")),
198        );
199        events.insert(
200            "ceiling_shot".to_owned(),
201            Value::Array(self.module_array("ceiling_shot", "events")),
202        );
203        events.insert(
204            "double_tap".to_owned(),
205            Value::Array(self.module_array("double_tap", "events")),
206        );
207        events.insert(
208            "fifty_fifty".to_owned(),
209            Value::Array(self.module_array("fifty_fifty", "events")),
210        );
211        events.insert(
212            "rush".to_owned(),
213            Value::Array(self.module_array("rush", "events")),
214        );
215        events.insert(
216            "speed_flip".to_owned(),
217            Value::Array(self.module_array("speed_flip", "events")),
218        );
219        events.insert(
220            "boost_pickups".to_owned(),
221            Value::Array(self.module_array("boost", "events")),
222        );
223        Value::Object(events)
224    }
225
226    fn timeline_config(&self) -> StatsTimelineConfig {
227        let positioning_config = self.config.get("positioning").and_then(Value::as_object);
228        let pressure_config = self.config.get("pressure").and_then(Value::as_object);
229        let rush_config = self.config.get("rush").and_then(Value::as_object);
230        let rush_defaults = RushCalculatorConfig::default();
231
232        StatsTimelineConfig {
233            most_back_forward_threshold_y: positioning_config
234                .and_then(|config| config.get("most_back_forward_threshold_y"))
235                .and_then(json_f32)
236                .unwrap_or(PositioningCalculatorConfig::default().most_back_forward_threshold_y),
237            level_ball_depth_margin: positioning_config
238                .and_then(|config| config.get("level_ball_depth_margin"))
239                .and_then(json_f32)
240                .unwrap_or(PositioningCalculatorConfig::default().level_ball_depth_margin),
241            pressure_neutral_zone_half_width_y: pressure_config
242                .and_then(|config| config.get("pressure_neutral_zone_half_width_y"))
243                .and_then(json_f32)
244                .unwrap_or(PressureCalculatorConfig::default().neutral_zone_half_width_y),
245            rush_max_start_y: rush_config
246                .and_then(|config| config.get("rush_max_start_y"))
247                .and_then(json_f32)
248                .unwrap_or(rush_defaults.max_start_y),
249            rush_attack_support_distance_y: rush_config
250                .and_then(|config| config.get("rush_attack_support_distance_y"))
251                .and_then(json_f32)
252                .unwrap_or(rush_defaults.attack_support_distance_y),
253            rush_defender_distance_y: rush_config
254                .and_then(|config| config.get("rush_defender_distance_y"))
255                .and_then(json_f32)
256                .unwrap_or(rush_defaults.defender_distance_y),
257            rush_min_possession_retained_seconds: rush_config
258                .and_then(|config| config.get("rush_min_possession_retained_seconds"))
259                .and_then(json_f32)
260                .unwrap_or(rush_defaults.min_possession_retained_seconds),
261        }
262    }
263
264    fn timeline_config_value(&self) -> SubtrActorResult<Value> {
265        let positioning_config = self.config.get("positioning").and_then(Value::as_object);
266        let pressure_config = self.config.get("pressure").and_then(Value::as_object);
267        let rush_config = self.config.get("rush").and_then(Value::as_object);
268
269        let mut config = Map::new();
270        config.insert(
271            "most_back_forward_threshold_y".to_owned(),
272            serialize_to_json_value(
273                &positioning_config
274                    .and_then(|config| config.get("most_back_forward_threshold_y"))
275                    .and_then(Value::as_f64)
276                    .unwrap_or(
277                        PositioningCalculatorConfig::default().most_back_forward_threshold_y as f64,
278                    ),
279            )?,
280        );
281        config.insert(
282            "level_ball_depth_margin".to_owned(),
283            serialize_to_json_value(
284                &positioning_config
285                    .and_then(|config| config.get("level_ball_depth_margin"))
286                    .and_then(Value::as_f64)
287                    .unwrap_or(
288                        PositioningCalculatorConfig::default().level_ball_depth_margin as f64,
289                    ),
290            )?,
291        );
292        config.insert(
293            "pressure_neutral_zone_half_width_y".to_owned(),
294            serialize_to_json_value(
295                &pressure_config
296                    .and_then(|config| config.get("pressure_neutral_zone_half_width_y"))
297                    .and_then(Value::as_f64)
298                    .unwrap_or(
299                        PressureCalculatorConfig::default().neutral_zone_half_width_y as f64,
300                    ),
301            )?,
302        );
303        let rush_defaults = RushCalculatorConfig::default();
304        config.insert(
305            "rush_max_start_y".to_owned(),
306            serialize_to_json_value(
307                &rush_config
308                    .and_then(|config| config.get("rush_max_start_y"))
309                    .and_then(Value::as_f64)
310                    .unwrap_or(rush_defaults.max_start_y as f64),
311            )?,
312        );
313        config.insert(
314            "rush_attack_support_distance_y".to_owned(),
315            serialize_to_json_value(
316                &rush_config
317                    .and_then(|config| config.get("rush_attack_support_distance_y"))
318                    .and_then(Value::as_f64)
319                    .unwrap_or(rush_defaults.attack_support_distance_y as f64),
320            )?,
321        );
322        config.insert(
323            "rush_defender_distance_y".to_owned(),
324            serialize_to_json_value(
325                &rush_config
326                    .and_then(|config| config.get("rush_defender_distance_y"))
327                    .and_then(Value::as_f64)
328                    .unwrap_or(rush_defaults.defender_distance_y as f64),
329            )?,
330        );
331        config.insert(
332            "rush_min_possession_retained_seconds".to_owned(),
333            serialize_to_json_value(
334                &rush_config
335                    .and_then(|config| config.get("rush_min_possession_retained_seconds"))
336                    .and_then(Value::as_f64)
337                    .unwrap_or(rush_defaults.min_possession_retained_seconds as f64),
338            )?,
339        );
340        Ok(Value::Object(config))
341    }
342
343    fn timeline_frame_value(&self, frame: &StatsSnapshotFrame) -> SubtrActorResult<Value> {
344        let mut timeline = Map::new();
345        timeline.insert(
346            "frame_number".to_owned(),
347            serialize_to_json_value(&frame.frame_number)?,
348        );
349        timeline.insert("time".to_owned(), serialize_to_json_value(&frame.time)?);
350        timeline.insert("dt".to_owned(), serialize_to_json_value(&frame.dt)?);
351        timeline.insert(
352            "seconds_remaining".to_owned(),
353            serialize_to_json_value(&frame.seconds_remaining)?,
354        );
355        timeline.insert(
356            "game_state".to_owned(),
357            serialize_to_json_value(&frame.game_state)?,
358        );
359        timeline.insert(
360            "gameplay_phase".to_owned(),
361            serialize_to_json_value(&frame.gameplay_phase)?,
362        );
363        timeline.insert(
364            "is_live_play".to_owned(),
365            serialize_to_json_value(&frame.is_live_play)?,
366        );
367        timeline.insert(
368            "fifty_fifty".to_owned(),
369            self.frame_stats_or_default::<FiftyFiftyStats>(frame, "fifty_fifty"),
370        );
371        timeline.insert(
372            "possession".to_owned(),
373            self.frame_stats_or_default::<PossessionStats>(frame, "possession"),
374        );
375        timeline.insert(
376            "pressure".to_owned(),
377            self.frame_stats_or_default::<PressureStats>(frame, "pressure"),
378        );
379        timeline.insert(
380            "rush".to_owned(),
381            self.frame_stats_or_default::<RushStats>(frame, "rush"),
382        );
383        timeline.insert(
384            "team_zero".to_owned(),
385            self.timeline_team_value(frame, "team_zero")?,
386        );
387        timeline.insert(
388            "team_one".to_owned(),
389            self.timeline_team_value(frame, "team_one")?,
390        );
391        timeline.insert(
392            "players".to_owned(),
393            Value::Array(
394                self.replay_meta
395                    .player_order()
396                    .map(|player| self.timeline_player_value(frame, player))
397                    .collect::<SubtrActorResult<Vec<_>>>()?,
398            ),
399        );
400        Ok(Value::Object(timeline))
401    }
402
403    pub(crate) fn replay_stats_frame(
404        &self,
405        frame: &StatsSnapshotFrame,
406    ) -> SubtrActorResult<ReplayStatsFrame> {
407        Ok(ReplayStatsFrame {
408            frame_number: frame.frame_number,
409            time: frame.time,
410            dt: frame.dt,
411            seconds_remaining: frame.seconds_remaining,
412            game_state: frame.game_state,
413            gameplay_phase: frame.gameplay_phase,
414            is_live_play: frame.is_live_play,
415            team_zero: self.replay_team_stats(frame, "team_zero")?,
416            team_one: self.replay_team_stats(frame, "team_one")?,
417            players: self
418                .replay_meta
419                .player_order()
420                .map(|player| self.replay_player_stats(frame, player))
421                .collect::<SubtrActorResult<Vec<_>>>()?,
422        })
423    }
424
425    fn replay_team_stats(
426        &self,
427        frame: &StatsSnapshotFrame,
428        team_key: &str,
429    ) -> SubtrActorResult<TeamStatsSnapshot> {
430        let is_team_zero = team_key == "team_zero";
431        Ok(TeamStatsSnapshot {
432            fifty_fifty: self
433                .frame_stats_or_default_typed::<FiftyFiftyStats>(frame, "fifty_fifty")?
434                .for_team(is_team_zero),
435            possession: self
436                .frame_stats_or_default_typed::<PossessionStats>(frame, "possession")?
437                .for_team(is_team_zero),
438            pressure: self
439                .frame_stats_or_default_typed::<PressureStats>(frame, "pressure")?
440                .for_team(is_team_zero),
441            rush: self
442                .frame_stats_or_default_typed::<RushStats>(frame, "rush")?
443                .for_team(is_team_zero),
444            core: self.frame_team_stat_or_default_typed(frame, "core", team_key)?,
445            backboard: self.frame_team_stat_or_default_typed(frame, "backboard", team_key)?,
446            double_tap: self.frame_team_stat_or_default_typed(frame, "double_tap", team_key)?,
447            ball_carry: self.frame_team_stat_or_default_typed(frame, "ball_carry", team_key)?,
448            boost: self.frame_team_stat_or_default_typed(frame, "boost", team_key)?,
449            movement: self.frame_team_stat_or_default_typed(frame, "movement", team_key)?,
450            powerslide: self.frame_team_stat_or_default_typed(frame, "powerslide", team_key)?,
451            demo: self.frame_team_stat_or_default_typed(frame, "demo", team_key)?,
452        })
453    }
454
455    fn replay_player_stats(
456        &self,
457        frame: &StatsSnapshotFrame,
458        player: &PlayerInfo,
459    ) -> SubtrActorResult<PlayerStatsSnapshot> {
460        let player_key = player_info_key(player)?;
461        Ok(PlayerStatsSnapshot {
462            player_id: player.remote_id.clone(),
463            name: player.name.clone(),
464            is_team_0: self.is_team_zero_player(player),
465            core: self.frame_player_stat_or_default_typed_by_key(frame, "core", &player_key)?,
466            backboard: self.frame_player_stat_or_default_typed_by_key(
467                frame,
468                "backboard",
469                &player_key,
470            )?,
471            ceiling_shot: self.frame_player_stat_or_default_typed_by_key(
472                frame,
473                "ceiling_shot",
474                &player_key,
475            )?,
476            double_tap: self.frame_player_stat_or_default_typed_by_key(
477                frame,
478                "double_tap",
479                &player_key,
480            )?,
481            fifty_fifty: self.frame_player_stat_or_default_typed_by_key(
482                frame,
483                "fifty_fifty",
484                &player_key,
485            )?,
486            speed_flip: self.frame_player_stat_or_default_typed_by_key(
487                frame,
488                "speed_flip",
489                &player_key,
490            )?,
491            touch: if frame.modules.contains_key("touch") {
492                self.frame_player_stat_or_default_with_by_key(frame, "touch", &player_key, || {
493                    TouchStats::default().with_complete_labeled_touch_counts()
494                })?
495            } else {
496                self.frame_player_stat_or_default_typed_by_key(frame, "touch", &player_key)?
497            },
498            musty_flick: self.frame_player_stat_or_default_typed_by_key(
499                frame,
500                "musty_flick",
501                &player_key,
502            )?,
503            dodge_reset: self.frame_player_stat_or_default_typed_by_key(
504                frame,
505                "dodge_reset",
506                &player_key,
507            )?,
508            ball_carry: self.frame_player_stat_or_default_typed_by_key(
509                frame,
510                "ball_carry",
511                &player_key,
512            )?,
513            boost: self.frame_player_stat_or_default_typed_by_key(frame, "boost", &player_key)?,
514            movement: self.frame_player_stat_or_default_with_by_key(
515                frame,
516                "movement",
517                &player_key,
518                || MovementStats::default().with_complete_labeled_tracked_time(),
519            )?,
520            positioning: self.frame_player_stat_or_default_typed_by_key(
521                frame,
522                "positioning",
523                &player_key,
524            )?,
525            powerslide: self.frame_player_stat_or_default_typed_by_key(
526                frame,
527                "powerslide",
528                &player_key,
529            )?,
530            demo: self.frame_player_stat_or_default_typed_by_key(frame, "demo", &player_key)?,
531        })
532    }
533
534    fn is_team_zero_player(&self, player: &PlayerInfo) -> bool {
535        self.replay_meta
536            .team_zero
537            .iter()
538            .any(|team_player| team_player.remote_id == player.remote_id)
539    }
540
541    fn timeline_team_value(
542        &self,
543        frame: &StatsSnapshotFrame,
544        team_key: &str,
545    ) -> SubtrActorResult<Value> {
546        let is_team_zero = team_key == "team_zero";
547        let mut team = Map::new();
548        team.insert(
549            "fifty_fifty".to_owned(),
550            serialize_to_json_value(
551                &self
552                    .frame_stats_or_default_typed::<FiftyFiftyStats>(frame, "fifty_fifty")?
553                    .for_team(is_team_zero),
554            )?,
555        );
556        team.insert(
557            "possession".to_owned(),
558            serialize_to_json_value(
559                &self
560                    .frame_stats_or_default_typed::<PossessionStats>(frame, "possession")?
561                    .for_team(is_team_zero),
562            )?,
563        );
564        team.insert(
565            "pressure".to_owned(),
566            serialize_to_json_value(
567                &self
568                    .frame_stats_or_default_typed::<PressureStats>(frame, "pressure")?
569                    .for_team(is_team_zero),
570            )?,
571        );
572        team.insert(
573            "rush".to_owned(),
574            serialize_to_json_value(
575                &self
576                    .frame_stats_or_default_typed::<RushStats>(frame, "rush")?
577                    .for_team(is_team_zero),
578            )?,
579        );
580        team.insert(
581            "core".to_owned(),
582            self.frame_team_stat_or_default::<CoreTeamStats>(frame, "core", team_key),
583        );
584        team.insert(
585            "backboard".to_owned(),
586            self.frame_team_stat_or_default::<BackboardTeamStats>(frame, "backboard", team_key),
587        );
588        team.insert(
589            "double_tap".to_owned(),
590            self.frame_team_stat_or_default::<DoubleTapTeamStats>(frame, "double_tap", team_key),
591        );
592        team.insert(
593            "ball_carry".to_owned(),
594            self.frame_team_stat_or_default::<BallCarryStats>(frame, "ball_carry", team_key),
595        );
596        team.insert(
597            "boost".to_owned(),
598            self.frame_team_stat_or_default::<BoostStats>(frame, "boost", team_key),
599        );
600        team.insert(
601            "movement".to_owned(),
602            self.frame_team_stat_or_default::<MovementStats>(frame, "movement", team_key),
603        );
604        team.insert(
605            "powerslide".to_owned(),
606            self.frame_team_stat_or_default::<PowerslideStats>(frame, "powerslide", team_key),
607        );
608        team.insert(
609            "demo".to_owned(),
610            self.frame_team_stat_or_default::<DemoTeamStats>(frame, "demo", team_key),
611        );
612        Ok(Value::Object(team))
613    }
614
615    fn timeline_player_value(
616        &self,
617        frame: &StatsSnapshotFrame,
618        player: &PlayerInfo,
619    ) -> SubtrActorResult<Value> {
620        let player_key = player_info_key(player)?;
621        let mut player_value = Map::new();
622        player_value.insert(
623            "player_id".to_owned(),
624            serialize_to_json_value(&player.remote_id)?,
625        );
626        player_value.insert("name".to_owned(), serialize_to_json_value(&player.name)?);
627        player_value.insert(
628            "is_team_0".to_owned(),
629            serialize_to_json_value(
630                &self
631                    .replay_meta
632                    .team_zero
633                    .iter()
634                    .any(|team_player| team_player.remote_id == player.remote_id),
635            )?,
636        );
637        player_value.insert(
638            "core".to_owned(),
639            self.frame_player_stat_or_default_by_key::<CorePlayerStats>(
640                frame,
641                "core",
642                &player_key,
643            )?,
644        );
645        player_value.insert(
646            "backboard".to_owned(),
647            self.frame_player_stat_or_default_by_key::<BackboardPlayerStats>(
648                frame,
649                "backboard",
650                &player_key,
651            )?,
652        );
653        player_value.insert(
654            "ceiling_shot".to_owned(),
655            self.frame_player_stat_or_default_by_key::<CeilingShotStats>(
656                frame,
657                "ceiling_shot",
658                &player_key,
659            )?,
660        );
661        player_value.insert(
662            "double_tap".to_owned(),
663            self.frame_player_stat_or_default_by_key::<DoubleTapPlayerStats>(
664                frame,
665                "double_tap",
666                &player_key,
667            )?,
668        );
669        player_value.insert(
670            "fifty_fifty".to_owned(),
671            self.frame_player_stat_or_default_by_key::<FiftyFiftyPlayerStats>(
672                frame,
673                "fifty_fifty",
674                &player_key,
675            )?,
676        );
677        player_value.insert(
678            "speed_flip".to_owned(),
679            self.frame_player_stat_or_default_by_key::<SpeedFlipStats>(
680                frame,
681                "speed_flip",
682                &player_key,
683            )?,
684        );
685        player_value.insert(
686            "touch".to_owned(),
687            self.frame_player_stat_or_value_by_key(
688                frame,
689                "touch",
690                &player_key,
691                if frame.modules.contains_key("touch") {
692                    serialize_to_json_value(
693                        &TouchStats::default().with_complete_labeled_touch_counts(),
694                    )?
695                } else {
696                    default_json_value::<TouchStats>()
697                },
698            )?,
699        );
700        player_value.insert(
701            "musty_flick".to_owned(),
702            self.frame_player_stat_or_default_by_key::<MustyFlickStats>(
703                frame,
704                "musty_flick",
705                &player_key,
706            )?,
707        );
708        player_value.insert(
709            "dodge_reset".to_owned(),
710            self.frame_player_stat_or_default_by_key::<DodgeResetStats>(
711                frame,
712                "dodge_reset",
713                &player_key,
714            )?,
715        );
716        player_value.insert(
717            "ball_carry".to_owned(),
718            self.frame_player_stat_or_default_by_key::<BallCarryStats>(
719                frame,
720                "ball_carry",
721                &player_key,
722            )?,
723        );
724        player_value.insert(
725            "boost".to_owned(),
726            self.frame_player_stat_or_default_by_key::<BoostStats>(frame, "boost", &player_key)?,
727        );
728        player_value.insert(
729            "movement".to_owned(),
730            self.frame_player_stat_or_value_by_key(
731                frame,
732                "movement",
733                &player_key,
734                if frame.modules.contains_key("movement") {
735                    serialize_to_json_value(
736                        &MovementStats::default().with_complete_labeled_tracked_time(),
737                    )?
738                } else {
739                    default_json_value::<MovementStats>()
740                },
741            )?,
742        );
743        player_value.insert(
744            "positioning".to_owned(),
745            self.frame_player_stat_or_default_by_key::<PositioningStats>(
746                frame,
747                "positioning",
748                &player_key,
749            )?,
750        );
751        player_value.insert(
752            "powerslide".to_owned(),
753            self.frame_player_stat_or_default_by_key::<PowerslideStats>(
754                frame,
755                "powerslide",
756                &player_key,
757            )?,
758        );
759        player_value.insert(
760            "demo".to_owned(),
761            self.frame_player_stat_or_default_by_key::<DemoPlayerStats>(
762                frame,
763                "demo",
764                &player_key,
765            )?,
766        );
767        Ok(Value::Object(player_value))
768    }
769
770    fn frame_stats_or_default<T>(&self, frame: &StatsSnapshotFrame, module_name: &str) -> Value
771    where
772        T: Default + Serialize,
773    {
774        frame
775            .modules
776            .get(module_name)
777            .and_then(Value::as_object)
778            .and_then(|module| module.get("stats"))
779            .cloned()
780            .unwrap_or_else(|| default_json_value::<T>())
781    }
782
783    fn frame_team_stat_or_default<T>(
784        &self,
785        frame: &StatsSnapshotFrame,
786        module_name: &str,
787        team_key: &str,
788    ) -> Value
789    where
790        T: Default + Serialize,
791    {
792        frame
793            .modules
794            .get(module_name)
795            .and_then(Value::as_object)
796            .and_then(|module| module.get(team_key))
797            .cloned()
798            .unwrap_or_else(|| default_json_value::<T>())
799    }
800
801    fn frame_player_stat_or_default_by_key<T>(
802        &self,
803        frame: &StatsSnapshotFrame,
804        module_name: &str,
805        player_key: &str,
806    ) -> SubtrActorResult<Value>
807    where
808        T: Default + Serialize,
809    {
810        self.frame_player_stat_or_value_by_key(
811            frame,
812            module_name,
813            player_key,
814            default_json_value::<T>(),
815        )
816    }
817
818    fn frame_player_stat_or_value_by_key(
819        &self,
820        frame: &StatsSnapshotFrame,
821        module_name: &str,
822        player_key: &str,
823        default_value: Value,
824    ) -> SubtrActorResult<Value> {
825        Ok(
826            player_stats_value_for_key(frame.modules.get(module_name), player_key)?
827                .cloned()
828                .unwrap_or(default_value),
829        )
830    }
831
832    fn frame_stats_or_default_typed<T>(
833        &self,
834        frame: &StatsSnapshotFrame,
835        module_name: &str,
836    ) -> SubtrActorResult<T>
837    where
838        T: Default + DeserializeOwned + Serialize,
839    {
840        decode_json_value(self.frame_stats_or_default::<T>(frame, module_name))
841    }
842
843    fn frame_team_stat_or_default_typed<T>(
844        &self,
845        frame: &StatsSnapshotFrame,
846        module_name: &str,
847        team_key: &str,
848    ) -> SubtrActorResult<T>
849    where
850        T: Default + DeserializeOwned + Serialize,
851    {
852        decode_json_value(self.frame_team_stat_or_default::<T>(frame, module_name, team_key))
853    }
854
855    fn frame_player_stat_or_default_typed_by_key<T>(
856        &self,
857        frame: &StatsSnapshotFrame,
858        module_name: &str,
859        player_key: &str,
860    ) -> SubtrActorResult<T>
861    where
862        T: Default + DeserializeOwned + Serialize,
863    {
864        self.frame_player_stat_or_default_with_by_key(frame, module_name, player_key, T::default)
865    }
866
867    fn frame_player_stat_or_default_with_by_key<T, F>(
868        &self,
869        frame: &StatsSnapshotFrame,
870        module_name: &str,
871        player_key: &str,
872        default: F,
873    ) -> SubtrActorResult<T>
874    where
875        T: DeserializeOwned + Serialize,
876        F: FnOnce() -> T,
877    {
878        decode_json_value(self.frame_player_stat_or_value_by_key(
879            frame,
880            module_name,
881            player_key,
882            serialize_to_json_value(&default())?,
883        )?)
884    }
885
886    fn module_typed_array<T>(&self, module_name: &str, field: &str) -> SubtrActorResult<Vec<T>>
887    where
888        T: DeserializeOwned,
889    {
890        decode_json_value(Value::Array(self.module_array(module_name, field)))
891    }
892
893    fn module_player_events<T, F>(
894        &self,
895        module_name: &str,
896        field: &str,
897        parse: F,
898    ) -> SubtrActorResult<Vec<T>>
899    where
900        F: Fn(&Value) -> SubtrActorResult<T>,
901    {
902        self.module_array(module_name, field)
903            .iter()
904            .map(parse)
905            .collect()
906    }
907
908    fn module_array(&self, module_name: &str, field: &str) -> Vec<Value> {
909        self.modules
910            .get(module_name)
911            .and_then(Value::as_object)
912            .and_then(|module| module.get(field))
913            .and_then(Value::as_array)
914            .cloned()
915            .unwrap_or_default()
916    }
917}
918
919impl CapturedStatsData<ReplayStatsFrame> {
920    pub fn into_replay_stats_timeline(self) -> SubtrActorResult<ReplayStatsTimeline> {
921        let CapturedStatsData {
922            replay_meta,
923            config,
924            modules,
925            frames,
926        } = self;
927        CapturedStatsData::<StatsSnapshotFrame> {
928            replay_meta,
929            config,
930            modules,
931            frames: Vec::new(),
932        }
933        .into_replay_stats_timeline_with_frames(frames)
934    }
935}
936
937fn player_stats_value_for_key<'a>(
938    module: Option<&'a Value>,
939    player_key: &str,
940) -> SubtrActorResult<Option<&'a Value>> {
941    let Some(entries) = module
942        .and_then(Value::as_object)
943        .and_then(|module| module.get("player_stats"))
944        .and_then(Value::as_array)
945    else {
946        return Ok(None);
947    };
948
949    for entry in entries {
950        let Some(entry_object) = entry.as_object() else {
951            continue;
952        };
953        let Some(player_id) = entry_object.get("player_id") else {
954            continue;
955        };
956        let Some(player_stats) = entry_object.get("stats") else {
957            continue;
958        };
959        if player_id_key(player_id)? == player_key {
960            return Ok(Some(player_stats));
961        }
962    }
963
964    Ok(None)
965}
966
967fn player_info_key(player: &PlayerInfo) -> SubtrActorResult<String> {
968    player_id_key(&serialize_to_json_value(&player.remote_id)?)
969}
970
971fn player_id_key(player_id: &Value) -> SubtrActorResult<String> {
972    serde_json::to_string(player_id).map_err(|error| {
973        SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
974            error.to_string(),
975        ))
976    })
977}
978
979fn default_json_value<T>() -> Value
980where
981    T: Default + Serialize,
982{
983    serde_json::to_value(T::default()).expect("default stats should serialize to json")
984}
985
986fn decode_json_value<T>(value: Value) -> SubtrActorResult<T>
987where
988    T: DeserializeOwned,
989{
990    serde_json::from_value(value).map_err(|error| {
991        SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
992            error.to_string(),
993        ))
994    })
995}
996
997fn parse_timeline_event(value: &Value) -> SubtrActorResult<TimelineEvent> {
998    let object = json_object(value, "timeline event")?;
999    Ok(TimelineEvent {
1000        time: json_required_f32(object, "time")?,
1001        kind: decode_json_value(json_required_value(object, "kind")?.clone())?,
1002        player_id: json_optional_remote_id(object.get("player_id"))?,
1003        is_team_0: json_optional_bool(object.get("is_team_0")),
1004    })
1005}
1006
1007fn parse_backboard_event(value: &Value) -> SubtrActorResult<BackboardBounceEvent> {
1008    let object = json_object(value, "backboard event")?;
1009    Ok(BackboardBounceEvent {
1010        time: json_required_f32(object, "time")?,
1011        frame: json_required_usize(object, "frame")?,
1012        player: json_required_remote_id(object, "player")?,
1013        is_team_0: json_required_bool(object, "is_team_0")?,
1014    })
1015}
1016
1017fn parse_ceiling_shot_event(value: &Value) -> SubtrActorResult<CeilingShotEvent> {
1018    let object = json_object(value, "ceiling shot event")?;
1019    Ok(CeilingShotEvent {
1020        time: json_required_f32(object, "time")?,
1021        frame: json_required_usize(object, "frame")?,
1022        player: json_required_remote_id(object, "player")?,
1023        is_team_0: json_required_bool(object, "is_team_0")?,
1024        ceiling_contact_time: json_required_f32(object, "ceiling_contact_time")?,
1025        ceiling_contact_frame: json_required_usize(object, "ceiling_contact_frame")?,
1026        time_since_ceiling_contact: json_required_f32(object, "time_since_ceiling_contact")?,
1027        ceiling_contact_position: json_required_vec3(object, "ceiling_contact_position")?,
1028        touch_position: json_required_vec3(object, "touch_position")?,
1029        local_ball_position: json_required_vec3(object, "local_ball_position")?,
1030        separation_from_ceiling: json_required_f32(object, "separation_from_ceiling")?,
1031        roof_alignment: json_required_f32(object, "roof_alignment")?,
1032        forward_alignment: json_required_f32(object, "forward_alignment")?,
1033        forward_approach_speed: json_required_f32(object, "forward_approach_speed")?,
1034        ball_speed_change: json_required_f32(object, "ball_speed_change")?,
1035        confidence: json_required_f32(object, "confidence")?,
1036    })
1037}
1038
1039fn parse_double_tap_event(value: &Value) -> SubtrActorResult<DoubleTapEvent> {
1040    let object = json_object(value, "double tap event")?;
1041    Ok(DoubleTapEvent {
1042        time: json_required_f32(object, "time")?,
1043        frame: json_required_usize(object, "frame")?,
1044        player: json_required_remote_id(object, "player")?,
1045        is_team_0: json_required_bool(object, "is_team_0")?,
1046        backboard_time: json_required_f32(object, "backboard_time")?,
1047        backboard_frame: json_required_usize(object, "backboard_frame")?,
1048    })
1049}
1050
1051fn parse_fifty_fifty_event(value: &Value) -> SubtrActorResult<FiftyFiftyEvent> {
1052    let object = json_object(value, "fifty fifty event")?;
1053    Ok(FiftyFiftyEvent {
1054        start_time: json_required_f32(object, "start_time")?,
1055        start_frame: json_required_usize(object, "start_frame")?,
1056        resolve_time: json_required_f32(object, "resolve_time")?,
1057        resolve_frame: json_required_usize(object, "resolve_frame")?,
1058        is_kickoff: json_required_bool(object, "is_kickoff")?,
1059        team_zero_player: json_optional_remote_id(object.get("team_zero_player"))?,
1060        team_one_player: json_optional_remote_id(object.get("team_one_player"))?,
1061        team_zero_position: json_required_vec3(object, "team_zero_position")?,
1062        team_one_position: json_required_vec3(object, "team_one_position")?,
1063        midpoint: json_required_vec3(object, "midpoint")?,
1064        plane_normal: json_required_vec3(object, "plane_normal")?,
1065        winning_team_is_team_0: json_optional_bool(object.get("winning_team_is_team_0")),
1066        possession_team_is_team_0: json_optional_bool(object.get("possession_team_is_team_0")),
1067    })
1068}
1069
1070fn parse_speed_flip_event(value: &Value) -> SubtrActorResult<SpeedFlipEvent> {
1071    let object = json_object(value, "speed flip event")?;
1072    Ok(SpeedFlipEvent {
1073        time: json_required_f32(object, "time")?,
1074        frame: json_required_usize(object, "frame")?,
1075        player: json_required_remote_id(object, "player")?,
1076        is_team_0: json_required_bool(object, "is_team_0")?,
1077        time_since_kickoff_start: json_required_f32(object, "time_since_kickoff_start")?,
1078        start_position: json_required_vec3(object, "start_position")?,
1079        end_position: json_required_vec3(object, "end_position")?,
1080        start_speed: json_required_f32(object, "start_speed")?,
1081        max_speed: json_required_f32(object, "max_speed")?,
1082        best_alignment: json_required_f32(object, "best_alignment")?,
1083        diagonal_score: json_required_f32(object, "diagonal_score")?,
1084        cancel_score: json_required_f32(object, "cancel_score")?,
1085        speed_score: json_required_f32(object, "speed_score")?,
1086        confidence: json_required_f32(object, "confidence")?,
1087    })
1088}
1089
1090fn parse_boost_pickup_comparison_event(
1091    value: &Value,
1092) -> SubtrActorResult<BoostPickupComparisonEvent> {
1093    let object = json_object(value, "boost pickup comparison event")?;
1094    Ok(BoostPickupComparisonEvent {
1095        comparison: decode_json_value(json_required_value(object, "comparison")?.clone())?,
1096        frame: json_required_usize(object, "frame")?,
1097        time: json_required_f32(object, "time")?,
1098        player_id: json_required_remote_id(object, "player_id")?,
1099        is_team_0: json_required_bool(object, "is_team_0")?,
1100        pad_type: decode_json_value(json_required_value(object, "pad_type")?.clone())?,
1101        field_half: decode_json_value(json_required_value(object, "field_half")?.clone())?,
1102        activity: decode_json_value(json_required_value(object, "activity")?.clone())?,
1103        reported_frame: json_optional_usize(object.get("reported_frame"))?,
1104        reported_time: json_optional_f32(object.get("reported_time"))?,
1105        inferred_frame: json_optional_usize(object.get("inferred_frame"))?,
1106        inferred_time: json_optional_f32(object.get("inferred_time"))?,
1107        boost_before: json_optional_f32(object.get("boost_before"))?,
1108        boost_after: json_optional_f32(object.get("boost_after"))?,
1109    })
1110}
1111
1112fn json_object<'a>(
1113    value: &'a Value,
1114    context: &str,
1115) -> SubtrActorResult<&'a serde_json::Map<String, Value>> {
1116    value.as_object().ok_or_else(|| {
1117        SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1118            "Expected {context} to be a JSON object"
1119        )))
1120    })
1121}
1122
1123fn json_required_value<'a>(
1124    object: &'a serde_json::Map<String, Value>,
1125    field: &str,
1126) -> SubtrActorResult<&'a Value> {
1127    object.get(field).ok_or_else(|| {
1128        SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1129            "Missing JSON field '{field}'"
1130        )))
1131    })
1132}
1133
1134fn json_f32(value: &Value) -> Option<f32> {
1135    value.as_f64().map(|number| number as f32)
1136}
1137
1138fn json_required_f32(
1139    object: &serde_json::Map<String, Value>,
1140    field: &str,
1141) -> SubtrActorResult<f32> {
1142    json_f32(json_required_value(object, field)?).ok_or_else(|| {
1143        SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1144            "Expected JSON field '{field}' to be a float"
1145        )))
1146    })
1147}
1148
1149fn json_required_usize(
1150    object: &serde_json::Map<String, Value>,
1151    field: &str,
1152) -> SubtrActorResult<usize> {
1153    json_required_value(object, field)?
1154        .as_u64()
1155        .map(|number| number as usize)
1156        .ok_or_else(|| {
1157            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1158                "Expected JSON field '{field}' to be an unsigned integer"
1159            )))
1160        })
1161}
1162
1163fn json_required_bool(
1164    object: &serde_json::Map<String, Value>,
1165    field: &str,
1166) -> SubtrActorResult<bool> {
1167    json_required_value(object, field)?
1168        .as_bool()
1169        .ok_or_else(|| {
1170            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1171                "Expected JSON field '{field}' to be a bool"
1172            )))
1173        })
1174}
1175
1176fn json_optional_bool(value: Option<&Value>) -> Option<bool> {
1177    value.and_then(Value::as_bool)
1178}
1179
1180fn json_optional_f32(value: Option<&Value>) -> SubtrActorResult<Option<f32>> {
1181    match value {
1182        None | Some(Value::Null) => Ok(None),
1183        Some(value) => json_f32(value).map(Some).ok_or_else(|| {
1184            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
1185                "Expected optional JSON value to be a float".to_owned(),
1186            ))
1187        }),
1188    }
1189}
1190
1191fn json_optional_usize(value: Option<&Value>) -> SubtrActorResult<Option<usize>> {
1192    match value {
1193        None | Some(Value::Null) => Ok(None),
1194        Some(value) => value
1195            .as_u64()
1196            .map(|number| Some(number as usize))
1197            .ok_or_else(|| {
1198                SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
1199                    "Expected optional JSON value to be an unsigned integer".to_owned(),
1200                ))
1201            }),
1202    }
1203}
1204
1205fn json_required_vec3(
1206    object: &serde_json::Map<String, Value>,
1207    field: &str,
1208) -> SubtrActorResult<[f32; 3]> {
1209    let array = json_required_value(object, field)?
1210        .as_array()
1211        .ok_or_else(|| {
1212            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1213                "Expected JSON field '{field}' to be a 3-element array"
1214            )))
1215        })?;
1216    if array.len() != 3 {
1217        return SubtrActorError::new_result(SubtrActorErrorVariant::StatsSerializationError(
1218            format!("Expected JSON field '{field}' to contain exactly 3 elements"),
1219        ));
1220    }
1221    Ok([
1222        json_f32(&array[0]).ok_or_else(|| {
1223            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1224                "Expected JSON field '{field}[0]' to be a float"
1225            )))
1226        })?,
1227        json_f32(&array[1]).ok_or_else(|| {
1228            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1229                "Expected JSON field '{field}[1]' to be a float"
1230            )))
1231        })?,
1232        json_f32(&array[2]).ok_or_else(|| {
1233            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(format!(
1234                "Expected JSON field '{field}[2]' to be a float"
1235            )))
1236        })?,
1237    ])
1238}
1239
1240fn json_required_remote_id(
1241    object: &serde_json::Map<String, Value>,
1242    field: &str,
1243) -> SubtrActorResult<PlayerId> {
1244    json_remote_id(json_required_value(object, field)?)
1245}
1246
1247fn json_optional_remote_id(value: Option<&Value>) -> SubtrActorResult<Option<PlayerId>> {
1248    match value {
1249        None | Some(Value::Null) => Ok(None),
1250        Some(value) => Ok(Some(json_remote_id(value)?)),
1251    }
1252}
1253
1254fn json_remote_id(value: &Value) -> SubtrActorResult<PlayerId> {
1255    let object = json_object(value, "remote id")?;
1256    if object.len() != 1 {
1257        return SubtrActorError::new_result(SubtrActorErrorVariant::StatsSerializationError(
1258            "Expected remote id to contain exactly one variant".to_owned(),
1259        ));
1260    }
1261
1262    let (variant, payload) = object.iter().next().expect("validated single variant");
1263    match variant.as_str() {
1264        "PlayStation" => {
1265            let payload = json_object(payload, "playstation remote id")?;
1266            Ok(RemoteId::PlayStation(Ps4Id {
1267                online_id: json_u64(json_required_value(payload, "online_id")?)?,
1268                name: json_required_value(payload, "name")?
1269                    .as_str()
1270                    .ok_or_else(|| {
1271                        SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
1272                            "Expected PlayStation name to be a string".to_owned(),
1273                        ))
1274                    })?
1275                    .to_owned(),
1276                unknown1: json_u8_vec(json_required_value(payload, "unknown1")?)?,
1277            }))
1278        }
1279        "PsyNet" => {
1280            let payload = json_object(payload, "psynet remote id")?;
1281            Ok(RemoteId::PsyNet(PsyNetId {
1282                online_id: json_u64(json_required_value(payload, "online_id")?)?,
1283                unknown1: json_u8_vec(json_required_value(payload, "unknown1")?)?,
1284            }))
1285        }
1286        "SplitScreen" => Ok(RemoteId::SplitScreen(json_u64(payload)? as u32)),
1287        "Steam" => Ok(RemoteId::Steam(json_u64(payload)?)),
1288        "Switch" => {
1289            let payload = json_object(payload, "switch remote id")?;
1290            Ok(RemoteId::Switch(SwitchId {
1291                online_id: json_u64(json_required_value(payload, "online_id")?)?,
1292                unknown1: json_u8_vec(json_required_value(payload, "unknown1")?)?,
1293            }))
1294        }
1295        "Xbox" => Ok(RemoteId::Xbox(json_u64(payload)?)),
1296        "QQ" => Ok(RemoteId::QQ(json_u64(payload)?)),
1297        "Epic" => Ok(RemoteId::Epic(
1298            payload
1299                .as_str()
1300                .ok_or_else(|| {
1301                    SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
1302                        "Expected Epic remote id payload to be a string".to_owned(),
1303                    ))
1304                })?
1305                .to_owned(),
1306        )),
1307        variant => SubtrActorError::new_result(SubtrActorErrorVariant::StatsSerializationError(
1308            format!("Unknown remote id variant '{variant}'"),
1309        )),
1310    }
1311}
1312
1313fn json_u64(value: &Value) -> SubtrActorResult<u64> {
1314    value
1315        .as_u64()
1316        .or_else(|| value.as_str().and_then(|text| text.parse().ok()))
1317        .ok_or_else(|| {
1318            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
1319                "Expected JSON value to be a u64".to_owned(),
1320            ))
1321        })
1322}
1323
1324fn json_u8_vec(value: &Value) -> SubtrActorResult<Vec<u8>> {
1325    value
1326        .as_array()
1327        .ok_or_else(|| {
1328            SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
1329                "Expected JSON value to be an array of bytes".to_owned(),
1330            ))
1331        })?
1332        .iter()
1333        .map(|entry| {
1334            entry
1335                .as_u64()
1336                .and_then(|number| u8::try_from(number).ok())
1337                .ok_or_else(|| {
1338                    SubtrActorError::new(SubtrActorErrorVariant::StatsSerializationError(
1339                        "Expected JSON array entry to be a byte".to_owned(),
1340                    ))
1341                })
1342        })
1343        .collect()
1344}