2

I am trying to process an object that can be either an array of object or just the object. Using the code below only works when the naics is an object and not an array. What am I doing wrong?

Here is the shortest example I can come up with:

[{
        "section": "52.219-1.b",
        "naics": [{
                "naicsName": "Engineering Services",
                "isPrimary": true,
                "ExcpCounter": 1,
                "isSmallBusiness": "Y",
                "naicsCode": 541330
            },
            {
                "naicsName": "Military and Aerospace Equipment and Military Weapons",
                "isPrimary": true,
                "ExcpCounter": 2,
                "isSmallBusiness": "Y",
                "naicsCode": 541330
            }
        ]
    },
    {
        "section": "52.219-1.b",
        "naics": {
            "naicsName": "Janitorial Services",
            "isPrimary": true,
            "isSmallBusiness": "Y",
            "naicsCode": 561720
        }
    }
]

I will only have one of the types but I forced two in an array to force it into Quick Type.

My classes are:

[JsonProperty("naics", NullValueHandling = NullValueHandling.Ignore)]
public AnswerNaics Naics { get; set; }

public partial struct AnswerNaics
{
    public AnswerNaic Naic;
    public AnswerNaic[] NaicArray;

    public static implicit operator AnswerNaics(AnswerNaic Naic) => new AnswerNaics { Naic = Naic };
    public static implicit operator AnswerNaics(AnswerNaic[] NaicArray) => new AnswerNaics { NaicArray = NaicArray };
}

public partial class AnswerNaic
{
    [JsonProperty("naicsName")]
    public string NaicsName { get; set; }

    [JsonProperty("hasSizeChanged")]
    public string HasSizeChanged { get; set; }

    [JsonProperty("isPrimary")]
    public bool IsPrimary { get; set; }

    [JsonProperty("ExcpCounter", NullValueHandling = NullValueHandling.Ignore)]
    public long? ExcpCounter { get; set; }

    [JsonProperty("isSmallBusiness")]
    public string IsSmallBusiness { get; set; }

    [JsonProperty("naicsCode")]
    public string NaicsCode { get; set; }
}

internal class NaicsConverter : JsonConverter
{
    public override bool CanConvert(Type t) => t == typeof(AnswerNaics) || t == typeof(AnswerNaics?);

    public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                var objectValue = serializer.Deserialize<AnswerNaic>(reader);
                return new AnswerNaics { Naic = objectValue };
            case JsonToken.StartArray:
                var arrayValue = serializer.Deserialize<AnswerNaic[]>(reader);
                return new AnswerNaics { NaicArray = arrayValue };
        }
        throw new Exception("Cannot unmarshal type AnswerNaics");
    }

    public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
    {
        var value = (AnswerNaics)untypedValue;
        if (value.NaicArray != null)
        {
            serializer.Serialize(writer, value.NaicArray);
            return;
        }
        if (value.Naic != null)
        {
            serializer.Serialize(writer, value.Naic);
            return;
        }
        throw new Exception("Cannot marshal type Naics");
    }

    public static readonly NaicsConverter Singleton = new NaicsConverter();
}

I have more object or array nodes, but I am just trying to figure out one to be able to apply to all of them.

DavidG
  • 113,891
  • 12
  • 217
  • 223
KeithL
  • 5,348
  • 3
  • 19
  • 25
  • `object` is the base type that arrays and classes share. – Sam Axe Nov 05 '19 at 16:16
  • 2
    Is there no way you can get the JSON fixed? It's really a terrible idea to have it change form like this. – DavidG Nov 05 '19 at 16:19
  • I am a recipient of the JSON not the producer of it. I am only trying to handle it because I originally was placing into an array only of AnswerNaic[] and noticed failures for singles – KeithL Nov 05 '19 at 16:21
  • The linked answer for DUPLICATE is amazing!!!!!! A general class that can handle any data type / object! – KeithL Nov 06 '19 at 14:10

2 Answers2

3

Since you cannot change the incoming JSON, you're going to need a custom converter instead. Something like this for example:

public class NaicsConverter : JsonConverter
{
    public override bool CanConvert(Type t) => t == typeof(Naics);

    public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
    {
        var naics = new Naics();

        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                // We know this is an object, so serialise a single Naics
                naics.Add(serializer.Deserialize<Naic>(reader));
                break;

            case JsonToken.StartArray:
                // We know this is an object, so serialise multiple Naics
                foreach(var naic in serializer.Deserialize<List<Naic>>(reader))
                {
                    naics.Add(naic);
                }
                break;
        }

        return naics;
    }

    public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And the supporting classes:

public class Root
{
    public string Section { get; set; }

    [JsonConverter(typeof(NaicsConverter))]
    public Naics Naics { get; set; }
}

// This isn't ideal, but it's quick and dirty and should get you started
public class Naics : List<Naic>
{

}

public class Naic
{
    public string NaicsName { get; set; }
    public bool IsPrimary { get; set; }
    public string IsSmallBusiness { get; set; }
    public long NaicsCode { get; set; }
}

And finally, to deserialise:

var settings = new JsonSerializerSettings {Converters = {new NaicsConverter()}};

var root = JsonConvert.DeserializeObject<Root[]>(Json, settings);

Now your object will get serialised into the list, but as a single item.

DavidG
  • 113,891
  • 12
  • 217
  • 223
0

You can solve this using a dynamic field in your class.

Consider this JSON:

[
  {
    "field1": "val1",
    "nested": [
      {
        "nestedField": "val2"
      },
      {
        "nestedField": "val3"
      }
    ]
  },
  {
    "field1": "val4",
    "nested": 
      {
        "nestedField": "val5"
      }          
  }
]

nested field is first an array with 2 objects and in the second appearance a single object. (similar to the JSON you posted)

So the class representation would look like:

    public class RootObject
    {
        public string field1 { get; set; }

        public dynamic nested { get; set; }  

        public List<NestedObject> NestedObjects
        {
            get
            {
               if(nested is JArray)
               {
                    return JsonConvert.DeserializeObject<List<NestedObject>>(nested.ToString());
               }

               var obj = JsonConvert.DeserializeObject<NestedObject>(nested.ToString());
               return new List<NestedObject> { obj };
            }
        }
    }

    public class NestedObject
    {
        public string nestedField { get; set; }
    }

The Deserialization code is trivial using Newtonsoft JSON:

var objectList = JsonConvert.DeserializeObject<List<RootObject>>("some_json");

foreach(var v in objectList)
{
    foreach(var n in v.NestedObjects)
    {
        Console.WriteLine(n.nestedField);
    }
}

The only change is an implementation of NestedObjects ready only property. It check if the dynamic object is JArray or object. In any case, it returns a List of nested objects.

EylM
  • 5,967
  • 2
  • 16
  • 28