skip to Main Content

I work with a class that comes from a Nuget package, and therefore I can’t add properties to it.

Imagine this class:

public class Base
{
    public Data Data { get; set; }

   // Many other properties..
}

public class Data
{
    public string Value1 { get; set; }

    // Many other properties..
}

The JSON payloads I work with look like this:

{
    Data:
    {
       "Value1": "some value...",
       "Value2": "some other value...",
       // Many other properties...
    }
}

In other words, the JSON payloads contain a property that does not exist in the Nuget’s class – Value2.

I am looking for a way to deserialize the above JSON into a class, in a way that gives me access both to Base and its properties, and to Value2.

I am also in need of doing this both using Newtonsoft and System.Text.Json libraries.

I have tried creating a derived class, with a nested JsonProperty attribute ("Data/Value2") but that doesn’t work with either library:

public class Derived : Base
{
    [JsonProperty("Data/Value2")]
    [JsonPropertyName("Data/Value2")]
    public string Value2 { get; set; }
}

I have tried overriding Data with in a derived class – that works in Newtonsoft but I lose the base class’s Data, and it is not supported at all in System.Text.Json:

public class Derived : Base
{
    [JsonProperty("Data")]
    [JsonPropertyName("Data")]
    public Data Data2 { get; set; }
}

I have tried playing with some custom converters but haven’t been able to find a working solution.

What is the way to support an extra property (one that can’t be added to the class because it’s external), without losing access to the class’s existing properties, for both Newtonsoft and System.Text.Json libraries?

2

Answers


  1. Chosen as BEST ANSWER

    Ended up using a DTO, as suggested in the comments. This appears to be required, at least until a new, relevant version of the Nuget is realeased.


  2. I would not go with this approach, but will post for educational purposes.
    as mentioned in comments it would be simpler to serialize to class and map to another one.
    this example is showing Custom Json Converter .
    P.S. i wrote in a way, than you can put multiple Values3,4 etc. (still stiff code)

    void Main()
    {
        var x = new Derived() { Data = new Data() { Value1 = "xxx" }, Value2 = "aaaaa" };
        Console.WriteLine(x);
        var settings = new JsonSerializerSettings
        {
            Converters = { new CustomObjectConverter<Derived>() }
        };
        string json = JsonConvert.SerializeObject(x, 
                            Newtonsoft.Json.Formatting.Indented, 
                            new Newtonsoft.Json.JsonConverter[] { new CustomObjectConverter<Derived>() });
        Console.WriteLine(json);
    }
    
    #region classes
    public class Base
    {
        public Data Data { get; set; }
    
        // Many other properties..
    }
    
    public class Data
    {
        public string Value1 { get; set; }
    }
    
    public class Derived : Base
    {
        public string Value2 { get; set; }
    }
    #endregion classes
    
    public class CustomObjectConverter<TModel> : Newtonsoft.Json.JsonConverter<TModel> where TModel : class, new()
    {
        public override TModel? ReadJson(JsonReader reader, Type objectType, TModel? existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
        {
            if (!(serializer.ContractResolver.ResolveContract(objectType) is JsonObjectContract contract))
                throw new NotImplementedException("Contract is not a JsonObjectContract");
            var jObj = JObject.Load(reader);
    
            var value = existingValue ?? (TModel?)contract.DefaultCreator?.Invoke() ?? new TModel();
            serializer.Populate(jObj.CreateReader(), value);
            return value;
        }
    
        public override bool CanWrite => true;
        public override void WriteJson(JsonWriter writer, TModel? value, Newtonsoft.Json.JsonSerializer serializer)
        {
            JObject jo = new JObject();
            Type type = value.GetType();
            List<JProperty> jp = new List<JProperty>();
            string[] FIELDS = new string[] {"Value2"}; //can have several properties
    
            foreach (PropertyInfo prop in type.GetProperties())
            {
                if (prop.CanRead)
                {
                    object propVal = prop.GetValue(value, null);
                    if (propVal != null)
                    {
                        if (FIELDS.Contains(prop.Name))
                        {
                            jp.Add(new JProperty(prop.Name, propVal));
                        }
                        jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
                    }
                }
            }
    
            if (jp?.Count > 0)
            {
                jp.ForEach(fe => {
                    jo.RemoveFields(new string[] {fe.Name});
                    jo.SelectToken("Data").FirstOrDefault()?.AddAfterSelf(fe);
                });
            }
            jo.WriteTo(writer);
        }
    }
    
    public static class Ext
    {
        public static JToken RemoveFields(this JToken token, string[] fields)
        {
            JContainer container = token as JContainer;
            if (container == null) return token;
    
            List<JToken> removeList = new List<JToken>();
            foreach (JToken el in container.Children())
            {
                JProperty p = el as JProperty;
                if (p != null && fields.Contains(p.Name))
                {
                    removeList.Add(el);
                }
                el.RemoveFields(fields);
            }
    
            foreach (JToken el in removeList)
            {
                el.Remove();
            }
    
            return token;
        }
    }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search