3

Given the following class:

public class Config {
    public string Property1 {get; set;} = "foo";
    public string Property2 {get; set;} = "bar";
    public string Property3 {get; set;} = "baz";
    public string Property4 {get; set;} = "baz1";
}

I want to serialize an instance of this class into two separate JSON strings. Some of the properties should go into the first JSON string:

{
    "Property1": "foo",
    "Property2": "bar"
}

while the rest of the properties should go into the other JSON string:

{
    "Property3": "baz",
    "Property4": "baz1"
}

The division of properties is always the same, and properties will go either to one or the other.

(I can deserialize the two JSON strings back into a single object using JObject.Merge.)

Currently, I am writing to a pair of JObject instances, but that is a maintenance nightmare (json["Property1"] = x.Property1; json["Property2"] = x.Property2; etc.).

How can I do this in a way which is easier to maintain?

7
  • 1
    One solution is to split the class into two separate classes. Commented Sep 9, 2019 at 16:50
  • @Code-Apprentice Not for me. I'm using this class as a datacontext for WPF bindings, and it's far more convenient to have it in a single class. Commented Sep 9, 2019 at 16:52
  • I would fix the architecture that created this problem rather than trying to engineer around the problem of bad architecture Commented Sep 9, 2019 at 16:52
  • @DetectivePikachu I would think that introducing a new class just so I can write to two different strings is a worse architecture. :) Commented Sep 9, 2019 at 16:54
  • 1
    So if I understand correctly, you need to have a single datacontext for WPF bindings but on the other hand you need to serialize the class as two separate JSON objects. These two requirements are in tension with each other. I think you can still solve this by separating the class into two classes. Then you can aggregate them in another parent class to combine them together. Commented Sep 9, 2019 at 16:59

3 Answers 3

3

Making life complicated for no reason but whatever. Here is one way to solve the problem:

  1. Create your own attribute class. Can be either one class per serialization "batch" or one class with the name of the serialization. For example, the name of the attribute class could be JsonSerializationBatchAttribute

  2. Decorate your data members with that attribute like this:

    public class Config {
        [JsonSerializationBatch("1")]
        public string Property1 {get; set;} = "foo";
        [JsonSerializationBatch("1")]
        public string Property2 {get; set;} = "bar";
        [JsonSerializationBatch("2")]
        public string Property3 {get; set;} = "baz";
        [JsonSerializationBatch("2")]
        public string Property4 {get; set;} = "baz1";
    }
  1. Then implement conditional serialization as described here:

https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm

in the serialization function check if a property has JsonSerializationBatch attribute with proper string and ignore all properties that do not.

I would do this complex thing only if I would have many objects that need this type of serialization. If only one object requires such serialization then I would lean towards splitting class into more than one or using anonymous objects for serialization.

Sign up to request clarification or add additional context in comments.

1 Comment

This is a step in the right direction, but see my answer.
1

You could try using anonymous objects, like so:

var string1 = JsonConvert.SerializeObject(new {property1: config.Property1, property2: config.Property2});
var string2 = JsonConvert.SerializeObject(new {property3: config.Property3, property4: config.Property4});

PS: I'm assuming you are using newtonsoft.json if not, just replace the serialize method.

Hope to have helped!

2 Comments

This isn't much more maintainable than setting values on the JObject directly. Every time I add a new property to the class, I have to remember to include it in one of the anonymous types.
True, the only way I can think of is creating two separate classes, but I don't think this solves your problem either...
1

I can write a class that inherits from Newtonsoft.Json.Serialization.DefaultContractResolver:

public class ConfigContractResolver : DefaultContractResolver {
    private static readonly string[] source1Names = new[] { 
        nameof(Config.Property1),
        nameof(Config.Property2)
    };
    public bool ForSource1 { get; set; } = true;

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) {
        var ret = base.CreateProperties(type, memberSerialization);
        var predicate = ForSource1 ? 
            x => source1Names.Contains(x.PropertyName) :
            (Func<JsonProperty, bool>)(x => !source1Names.Contains(x.PropertyName));
        ret = ret.Where(predicate).ToList();
        return ret;
    }
}

and serialize to both strings using an instance of ConfigContractResolver:

Config config = new Config();

// ...

var resolver = new ConfigContractResolver();
var serializationSettings = new JsonSerializerSettings {
    ContractResolver = resolver
};
string forSource1 = JsonConvert.SerializeObject(config, serializationSettings);

// It would be nice to reuse the same contract resolver and settings objects
// but that doesn't seem to work -- https://github.com/JamesNK/Newtonsoft.Json/issues/2155

resolver = new ConfigContractResolver() {
    ForSource1 = false
};
serializationSettings = new JsonSerializerSettings {
    ContractResolver = resolver
};
resolver.ForSource1 = false;
string forSource2 = JsonConvert.SerializeObject(config, serializationSettings);

Source: IContractResolver example

3 Comments

This isn't much more maintainable than setting values on the JObject directly. Every time you add a new property to the class, you have to remember to include it in the list of source1Names. Decorating properties with an attribute means that adding a new property only changes code in one place.
@BurnsBA Not quite. Setting on JObject directly is more longwinded -- json["PropertyName"] = config.PropertyName -- two uses of the property name, and requires an assignment for each property either in source1 or source2. Your point about decorating properties is valid, but I see the serialization contract as something external to the class itself, and thus it's place is outside the class. It's also a little more complicated to query a property info for an attribute value, than to use the property name. But YMMV.
I would use nameof() instead of strings but still having related code in two places leads to bugs. Been there. Done that, i.e. bugs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.