Skip to content

Commit

Permalink
Simplify MapProperty serde representation
Browse files Browse the repository at this point in the history
Before:

      {
        "type": "MapProperty",
        "key_type": "StrProperty",
        "value_type": "StrProperty",
        "allocation_flags": 0,
        "value": [
          [
            {
              "type": "StrProperty",
              "value": "Story/Campaign/index"
            },
            {
              "type": "StrProperty",
              "value": "c1.m18b.s2"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "music"
            },
            {
              "type": "StrProperty",
              "value": "music.lasthope"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "preferedEngine"
            },
            {
              "type": "StrProperty",
              "value": "HybridEngine"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "Option_settings.a.master"
            },
            {
              "type": "StrProperty",
              "value": "50.0%"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "Option_settings.a.music"
            },
            {
              "type": "StrProperty",
              "value": "50.0%"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "Option_settings.g.terrainres"
            },
            {
              "type": "StrProperty",
              "value": "tfactor.x2"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "Option_settings.dpiscale"
            },
            {
              "type": "StrProperty",
              "value": "120%"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "Option_settings.daytime"
            },
            {
              "type": "StrProperty",
              "value": "toggle.enabled"
            }
          ],
          [
            {
              "type": "StrProperty",
              "value": "preferedTrainColor"
            },
            {
              "type": "StrProperty",
              "value": "Crimson"
            }
          ]
        ]
      }

After:

      {
        "type": "MapProperty",
        "key_type": "StrProperty",
        "value_type": "StrProperty",
        "values": {
          "Story/Campaign/index": "c1.m18b.s2",
          "music": "music.lasthope",
          "preferedEngine": "HybridEngine",
          "Option_settings.a.master": "50.0%",
          "Option_settings.a.music": "50.0%",
          "Option_settings.g.terrainres": "tfactor.x2",
          "Option_settings.dpiscale": "120%",
          "Option_settings.daytime": "toggle.enabled",
          "preferedTrainColor": "Crimson"
        }
      }
  • Loading branch information
scottanderson committed Jan 20, 2024
1 parent b5efffb commit 648ee49
Showing 1 changed file with 219 additions and 4 deletions.
223 changes: 219 additions & 4 deletions src/properties/map_property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,31 @@ use std::{
};

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};

use indexmap::IndexMap;
use serde::{
de::{self, Deserializer, MapAccess, Visitor},
ser::SerializeMap,
Deserialize, Serialize, Serializer,
};

use crate::{
cursor_ext::{ReadExt, WriteExt},
error::{DeserializeError, Error},
scoped_stack_entry::ScopedStackEntry,
types::Guid,
};

use super::{
impl_read_header, impl_write, impl_write_header_part, Property, PropertyOptions, PropertyTrait,
impl_read_header, impl_write, impl_write_header_part,
int_property::IntProperty,
name_property::NameProperty,
str_property::StrProperty,
struct_property::{StructProperty, StructPropertyValue},
Property, PropertyOptions, PropertyTrait,
};

/// A property that stores a map of properties to properties.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MapProperty {
/// Key type name.
pub key_type: String,
Expand All @@ -28,7 +37,6 @@ pub struct MapProperty {
/// Allocation flags.
pub allocation_flags: u32,
/// Map entries.
#[cfg_attr(feature = "serde", serde(with = "indexmap::serde_seq"))]
pub value: IndexMap<Property, Property>,
}

Expand Down Expand Up @@ -131,3 +139,210 @@ impl Hash for MapProperty {
self.allocation_flags.hash(state);
}
}

#[cfg(feature = "serde")]
impl Serialize for MapProperty {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let entry_count = if self.allocation_flags == 0 { 3 } else { 4 };
let mut map = serializer.serialize_map(Some(entry_count))?;
map.serialize_entry("key_type", &self.key_type)?;
map.serialize_entry("value_type", &self.value_type)?;
if self.allocation_flags != 0 {
map.serialize_entry("allocation_flags", &self.allocation_flags)?;
}
match (self.key_type.as_str(), self.value_type.as_str()) {
("NameProperty" | "StrProperty", "IntProperty") => {
let values: IndexMap<String, i32> = self
.value
.iter()
.map(|(key, value)| match (key, value) {
(
Property::NameProperty(NameProperty {
value: Some(key),
array_index: 0,
}),
Property::IntProperty(IntProperty { value }),
) => (key.to_string(), *value),
(
Property::StrProperty(StrProperty { value: Some(key) }),
Property::IntProperty(IntProperty { value }),
) => (key.to_string(), *value),
_ => unreachable!("Unexpected combination of Property variants."),
})
.collect();
map.serialize_entry("values", &values)?;
}
("NameProperty" | "StrProperty", "StrProperty") => {
let values: IndexMap<String, String> = self
.value
.iter()
.map(|(key, value)| match (key, value) {
(
Property::NameProperty(NameProperty {
value: Some(key),
array_index: 0,
}),
Property::StrProperty(StrProperty { value: Some(value) }),
) => (key.to_string(), value.to_string()),
(
Property::StrProperty(StrProperty { value: Some(key) }),
Property::StrProperty(StrProperty { value: Some(value) }),
) => (key.to_string(), value.to_string()),
_ => unreachable!("Unexpected combination of Property variants."),
})
.collect();
map.serialize_entry("values", &values)?;
}
("NameProperty" | "StrProperty", "StructProperty") => {
let values: IndexMap<String, &StructPropertyValue> = self
.value
.iter()
.map(|(key, value)| match (key, value) {
(
Property::NameProperty(NameProperty {
value: Some(key),
array_index: 0,
}),
Property::StructProperty(StructProperty {
guid: Guid([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
value,
}),
) => (key.to_string(), value),
(
Property::StrProperty(StrProperty { value: Some(key) }),
Property::StructProperty(StructProperty {
guid: Guid([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
value,
}),
) => (key.to_string(), value),
_ => unreachable!("Unexpected combination of Property variants."),
})
.collect();
map.serialize_entry("values", &values)?;
}
_ => Err(serde::ser::Error::custom(format!(
"Unsupported key and value types ({}, {})",
self.key_type, self.value_type
)))?,
};
map.end()
}
}

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for MapProperty {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MapPropertyVisitor;

impl<'de> Visitor<'de> for MapPropertyVisitor {
type Value = MapProperty;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("struct MapProperty")
}

fn visit_map<M>(self, mut map: M) -> Result<MapProperty, M::Error>
where
M: MapAccess<'de>,
{
let mut key_type: Option<String> = None;
let mut value_type: Option<String> = None;
let mut allocation_flags: u32 = 0;
let mut found_values = false;

while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"key_type" => {
key_type = Some(map.next_value()?);
}
"value_type" => {
value_type = Some(map.next_value()?);
}
"allocation_flags" => {
allocation_flags = map.next_value()?;
}
"values" => {
found_values = true;
break;
}
_ => Err(serde::de::Error::custom(format!("Unsupported key {}", key)))?,
}
}

let key_type = key_type.ok_or_else(|| de::Error::missing_field("key_type"))?;
let value_type =
value_type.ok_or_else(|| de::Error::missing_field("value_type"))?;
if !found_values {
Err(de::Error::missing_field("values"))?
}

// Convert values into the IndexMap<Property, Property>
let value: IndexMap<Property, Property> =
match (key_type.as_str(), value_type.as_str()) {
("NameProperty", "IntProperty") => map
.next_value::<IndexMap<String, i32>>()?
.into_iter()
.map(|(key, value)| {
(
Property::NameProperty(NameProperty::from(Some(key))),
Property::IntProperty(IntProperty { value }),
)
})
.collect(),
("StrProperty", "IntProperty") => map
.next_value::<IndexMap<String, i32>>()?
.into_iter()
.map(|(key, value)| {
(
Property::StrProperty(StrProperty { value: Some(key) }),
Property::IntProperty(IntProperty { value }),
)
})
.collect(),
("StrProperty", "StrProperty") => map
.next_value::<IndexMap<String, String>>()?
.into_iter()
.map(|(key, value)| {
(
Property::StrProperty(StrProperty { value: Some(key) }),
Property::StrProperty(StrProperty { value: Some(value) }),
)
})
.collect(),
("NameProperty", "StructProperty") => map
.next_value::<IndexMap<String, StructPropertyValue>>()?
.into_iter()
.map(|(key, value)| {
(
Property::NameProperty(NameProperty::from(Some(key))),
Property::StructProperty(StructProperty {
guid: Guid::from(0),
value,
}),
)
})
.collect(),
_ => Err(serde::de::Error::custom(format!(
"Unsupported key and value types ({}, {})",
key_type, value_type
)))?,
};

Ok(MapProperty {
key_type,
value_type,
allocation_flags,
value,
})
}
}

deserializer.deserialize_map(MapPropertyVisitor)
}
}

0 comments on commit 648ee49

Please sign in to comment.