2

I have a data model that is defined as a class in C#. I need to merge two objects using JObject.Merge, but in the case of one particular property I need to ignore empty string values from the 2nd object, similar to how you would ignore null values. Is there an existing attribute property for this, or do I somehow need to write my own?

void Main()
{
    string json1 = @"{ Foo: ""foo1"", Bar: ""bar1"" }";
    string json2 = @"{ Foo: ""foo2"", Bar: null }";
    string json3 = @"{ Foo: ""foo3"", Bar: """" }";

    var model1 = JObject.Parse(json1);
    var model2 = JObject.Parse(json2);
    model1.Merge(model2);
    model1.Dump();

    model1 = JObject.Parse(json1);
    model2 = JObject.Parse(json3);
    model1.Merge(model2);
    model1.Dump();
}

public class Model
{
    [JsonProperty("foo")]
    public string Foo { get ; set; }

    [JsonProperty("bar", NullValueHandling = NullValueHandling.Ignore)]
    public string Bar { get ; set; }

}

Output (1): Model.Bar = "bar1" 
Output (2): Model.Bar = "";
Desired output of (2): Model.Bar = "bar1"

EDIT: OK, I realized that attributes could not be applied since I needed to work with the raw json string only as input. This was mainly due to the fact that my classes had attributes with default values. Merging classes with empty values would trigger the defaults and end up overwriting the original, which is what I didn't want. I need to be able to take a partial json representation of a class and update the original. Sorry if that wasn't clear from the get-go. I'll update what I ended up doing an answer.

1
  • What do you mean by "Is there an existing attribute property for this"? A property in JsonMergeSettings? Or a property of JObject itself? Commented Apr 6, 2015 at 17:36

2 Answers 2

2

You could remove the properties with empty string values from the JObject you want to merge in, using the following extension methods:

public static class JsonExtensions
{
    public static void RemovePropertiesByValue(this JToken root, Predicate<JValue> filter)
    {
        var nulls = root.DescendantsAndSelf().OfType<JValue>().Where(v => v.Parent is JProperty && filter(v)).ToList();
        foreach (var value in nulls)
        {
            var parent = (JProperty)value.Parent;
            parent.Remove();
        }
    }

    public static IEnumerable<JToken> DescendantsAndSelf(this JToken node)
    {
        if (node == null)
            return Enumerable.Empty<JToken>();
        var container = node as JContainer;
        if (container != null)
            return container.DescendantsAndSelf();
        else
            return new [] { node };
    }
}

Then use it like:

        model2.RemovePropertiesByValue(v => v.Type == JTokenType.String && string.IsNullOrEmpty((string)v.Value));

Note this doesn't remove empty strings from arrays because that would mess up array indexing, and therefore merging. Do you need that also?

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

6 Comments

I'd prefer to decorate the property rather that create custom code for a single field.
@DanBailiff - the code is recursive, it cleans empty string values out of the entire JToken hierarchy. Also, at this point there is no property to decorate, you're working with JTokens not POCOs. The only setting in JsonMergeSettings is MergeArrayHandling which doesn't seem relevant.
You're right. I found a workaround or the "right way" by converting the json to objects and then using the serializer attributes for "DefaultValueHandling.Ignore" and "DefaultValue("")" when emitting the json to be merged. I don't know if I should update the question/solution or withdraw the question?
@DanBailiff - if you still need help, I'd suggest asking a new question, since it's more likely to get attention. BTW, would JsonConvert.PopulateObject help here?
@drzaus - JContainer.DescendantsAndSelf() is already recursive. But JValue (and the abstract base class JToken) don't implement DescendantsAndSelf() (despite the fact that it would make sense to do so) so JsonExtensions.DescendantsAndSelf() provides an implementation for non-container tokens that returns just the self.
|
1

I manipulated the JObject dictionary keys to massage the specific entry. I feel dirty, but it works. I can't think of a "good" way to do this.

model1 = JObject.Parse(json1);
model2 = JObject.Parse(json3);
IDictionary<string, JToken> dictionary = model2;
dictionary["Bar"] = string.IsNullOrEmpty((string)dictionary["Bar"])
            ? null
            : dictionary["Bar"];
model1.Merge(model2);

Comments

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.