-1

I get a whole bunch of "settings" from an API via JSON which looks like this:

{
    ...
    "SettingA": { "value": [
        [ 3200, 0  ], 
        [ 4300, 0  ], 
        [ 5600, 0  ], 
        [ 7000, 0  ]
    ], "type": "array", "readonly": 1},
    "SettingB": { "value": [
        [ 3, 3320, -0.6, "Auto WB"  ], 
        [ 7, 3200, 0, "Tungsten"  ], 
        [ 7, 4300, 0, "Fluorescent"  ], 
        [ 7, 5600, 0, "Daylight"  ], 
        [ 7, 7000, 0, "Daylight cool"  ]
    ], "type": "array", "readonly": 1}
    ...
}

Based on the keyword like "SettingA" or "SettingB", I need to serialize it's VALUE property to a specific type (if known, some settings can stay as JTOKEN or JARRAY, as I don't care)

Is there an elgant way of deserializing the VALUE to a corresponding type? Especially, because the properties are NOT NAMED, so I would have to assign properties by index.

Currently, I am doing this (which works, but requires lots of hand-writing):

foreach (JToken item in table)
{
    var p = new WhiteBalancePreset(item.Value<int>(0), item.Value<double>(1),
        item.Value<double>(2), item.Value<string>(3));
    if (p.State != 0)
        presets.Add(p);
}
uTILLIty
  • 473
  • 4
  • 9

1 Answers1

1

Firstly, you can use a non-generic version of ObjectToArrayConverter<T> from How to deserialize objects containing arrays of values with a fixed schema to strongly typed data classes? to serialize your SettingA and SettingB types from and to arrays of property values. The easiest way to do it is to define a base class for all such settings with the converter applied directly:

[JsonConverter(typeof(ObjectToArrayConverter))]
public abstract class SettingBase
{
}

public class ObjectToArrayConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }

    static bool ShouldSkip(JsonProperty property)
    {
        return property.Ignored || !property.Readable || !property.Writable;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var type = value.GetType();
        var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException("invalid type " + type.FullName);
        var list = contract.Properties.Where(p => !ShouldSkip(p)).Select(p => p.ValueProvider.GetValue(value));
        serializer.Serialize(writer, list);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var token = JToken.Load(reader);
        if (token.Type != JTokenType.Array)
            throw new JsonSerializationException("token was not an array");
        var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException("invalid type " + objectType.FullName);
        var value = existingValue ?? contract.DefaultCreator();
        foreach (var pair in contract.Properties.Where(p => !ShouldSkip(p)).Zip(token, (p, v) => new { Value = v, Property = p }))
        {
            var propertyValue = pair.Value.ToObject(pair.Property.PropertyType, serializer);
            pair.Property.ValueProvider.SetValue(value, propertyValue);
        }
        return value;
    }
}

Then define SettingA and SettingB as derived types:

public class SettingA : SettingBase
{
    [JsonProperty(Order = 1)]
    public int Property1 { get; set; }
    [JsonProperty(Order = 2)]
    public double Property2 { get; set; }
}

public class SettingB : SettingBase
{
    [JsonProperty(Order = 1)]
    public int Code { get; set; }
    [JsonProperty(Order = 2)]
    public double ColorTemperature { get; set; }
    [JsonProperty(Order = 3)]
    public double GammaCorrection { get; set; }
    [JsonProperty(Order = 4)]
    public string Name { get; set; }
}

They will now be serialized from and to JSON as arrays of property values rather than objects. Note that all properties must be marked with [JsonProperty(Order = XXX)] to be serialized reliably with this converter. [DataContract] and [DataMember(Order = XXX)] could be used instead as those are also supported by Newtonsoft.

The next stage, correctly deserializing the named JSON properties "SettingA" and "SettingB", can be handled by using generics as well. define the following types:

public class SettingsTable<TSettings> where TSettings : SettingBase
{
    public SettingsTable() { this.Value = new List<TSettings>(); }

    [JsonProperty("value")]
    public List<TSettings> Value { get; set; }

    [JsonProperty("type")]
    public string Type { get; set; }

    [JsonProperty("readonly")]
    public int Readonly { get; set; }
}

public class SettingsSet
{
    public SettingsSet() { this.OtherSettings = new Dictionary<string, JToken>(); }

    public SettingsTable<SettingA> SettingA { get; set; }

    public SettingsTable<SettingB> SettingB { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JToken> OtherSettings { get; set; }
}

The root type SettingsSet contains defined properties for the settings you are interested in, namely SettingA and SettingB. Unknown or uninteresting settings are captured by OtherSettings which is marked with [JsonExtensionData].

Sample fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340