C# Json Serializer Chokes on nested 'Shudown' value

I'm using the Newtonsoft JSON tools to deserialize a data request from an AF attribute, and it blows up when it reaches a nested value that is another JSON object with 'Shutdown' embedded in it, like this:

 

{"Timestamp":"2024-01-25T03:30:00Z","Value":{"Name":"Shutdown","Value":254,"IsSystem":true}}

 

In my serialization class I've tried using both 'object' and 'string' as the type for the Value property, but it chokes on both. So I am wondering if I need to filter out these values, and if so, how? Or is there another approach?

Parents
  • It would really help if you shared the relevant code for (1) the serialization class as well as (2) the snippet of HOW you are using it with a deserialize call. Let us know which version of .NET you are using. I have tested on .NET 6 and cannot reproduce your problem.

     

    The Value property should be an Object for 2 reasons. One, you may have many recorded values with different Value types (Integer, Single, Double), and Two, if a bad state is sent, then it would not match the expected Value type.

     

    I would encourage your class to include an IsGood property, if possible. Since the early 1990's, the OPC Foundation has declared a DA value to contain a minimum of 3 properties: (1) Time, (2) Value, and (3) Quality/Status.

     

    For simple enums, you may need to decorate the property, such as:

     

    [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]

    public object Value { get; set; }

     

    Though the ABOVE WILL NOT FIX your issue. You would use it when the property will always be an enum type.

     

    I want to know more about what is blowing up or throwing an exception when (A) Value is defined as object and (B) you are deserializing. Since you have provided ZERO code, I do not know if the actual deserialize call fails, or if it fails afterwards due to the complexity of Value.

     

Reply
  • It would really help if you shared the relevant code for (1) the serialization class as well as (2) the snippet of HOW you are using it with a deserialize call. Let us know which version of .NET you are using. I have tested on .NET 6 and cannot reproduce your problem.

     

    The Value property should be an Object for 2 reasons. One, you may have many recorded values with different Value types (Integer, Single, Double), and Two, if a bad state is sent, then it would not match the expected Value type.

     

    I would encourage your class to include an IsGood property, if possible. Since the early 1990's, the OPC Foundation has declared a DA value to contain a minimum of 3 properties: (1) Time, (2) Value, and (3) Quality/Status.

     

    For simple enums, you may need to decorate the property, such as:

     

    [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]

    public object Value { get; set; }

     

    Though the ABOVE WILL NOT FIX your issue. You would use it when the property will always be an enum type.

     

    I want to know more about what is blowing up or throwing an exception when (A) Value is defined as object and (B) you are deserializing. Since you have provided ZERO code, I do not know if the actual deserialize call fails, or if it fails afterwards due to the complexity of Value.

     

Children
  • EDIT: I am using .NET 4.7.2

     

    I have a generic static method to perform the HTTP request and subsequent deserialization of various objects such as AF attributes, elements, data servers, PI values, summary values, etc:

    public static T Fetch(String uri)
            {
                if (PIWebAPI.httpClient == null) throw new Exception("PI Web Api Client not initialized!");
                T deserializedClass = default;
                try
                {
                    var responseTask = PIWebAPI.httpClient.GetAsync(uri);
                    responseTask.Wait();
                    var result = responseTask.Result;
    
                    if (result.IsSuccessStatusCode)
                    {
                        var readTask = result.Content.ReadAsStringAsync();
                        readTask.Wait();
                        string strResult = readTask.Result;
                        deserializedClass = JsonConvert.DeserializeObject<T>(strResult);
                    }
                }
                catch(Exception ex)
                {
                    return default(T);
                }
                return deserializedClass;
            }

    And the PIValue class that is being deserialized in this problem is:

    /// <summary>
        /// Represents a timeseries data point from PI web API whether it's from a PIPoint or AFAttribute
        /// </summary>
        [JsonObject]
        public class PiValue2 : IPIWebApiClass
        {
            #region Fields
            private DateTime _Timestamp = DateTime.MinValue;
            private object _Value = 0d;
            #endregion
    
            #region Properties
            /// <summary>
            /// Always gets set to Local Time
            /// </summary>
            [JsonProperty]
            public DateTime Timestamp
            {
                get { return _Timestamp; }
                set
                {
                    _Timestamp = value.ToLocalTime();
                }
            }
            /// <summary>
            /// BEWARE this can be NaN from the web API if it comes from a calculated source where the denominator is zero, or if the returned value is a string
            /// </summary>
            [JsonProperty]
            public object Value
            {
                get { return _Value; }
                set
                {
                    if (_Value != value)
                    {
                        double val = 0;
                        if (value is null)
                            _Value = Double.NaN;
                        else if (Double.TryParse(value.ToString(), out val))
                            _Value = val;
                        else //string, enumeration set, etc
                            _Value = Double.NaN;
                    }
                }
            }
           
            public string UnitsAbbreviation { get; set; }
            public bool Good { get; set; }
            public bool Questionable { get; set; }
            public bool Substituted { get; set; }
            public bool Annotated { get; set; }
            #endregion
    
            #region Methods
            public override string ToString()
            {
                return $"[{Timestamp.ToString("G")}] {Value}";
            }
            #endregion
        }

     The 'Root' class is just there to facilitate the Items

        public class Root<T>
        {
            public List<T> Items { get; set; }
        }

    array:

     

     

     

     

  • I also captured the portion of the HTTP body that was causing the issue to verify it was the cause:

      String foo = "{\"Items\":[{\"Timestamp\":\"2024-01-01T08:00:00Z\",\"Value\":31.200000762939453},{\"Timestamp\":\"2024 - 01 - 25T03: 30:00Z\",\"Value\":{\"Name\":\"Shutdown\",\"Value\":254,\"IsSystem\":true}}]}";
                        Root<PiValue> fooClass = JsonConvert.DeserializeObject<Root<PiValue>>(foo);

    Which it was, and thows the same exception:

     

    "Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Object' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath 'Items[1].Value.Name', line 1, position 131."

     

     

  • I wrote a .NET Framework 4.7.2 app and CANNOT DUPLICATE the issue. This indicates to me that most likely YOU have a LOGIC bug of your own doing somewhere in the code that have not shared with us.

     

    // These class definitions are about as plain vanilla as you can get.
       
       internal class Root<T>
        {
            public List<T> Items { get; set; }  
        }
    
        internal class PiValue
        {
            public DateTime Timestamp { get; set; }
            public object Value { get; set; }
            public override string ToString() => $"{Value}, {Timestamp}";
        }

    And in Main(), I have

     

    String foo = "{\"Items\":[{\"Timestamp\":\"2024-01-01T08:00:00Z\",\"Value\":31.200000762939453},{\"Timestamp\":\"2024 - 01 - 25T03: 30:00Z\",\"Value\":{\"Name\":\"Shutdown\",\"Value\":254,\"IsSystem\":true}}]}";
    Root<PiValue> fooClass = JsonConvert.DeserializeObject<Root<PiValue>>(foo);
    Console.WriteLine(fooClass.Items.Count);

    And the Main() works perfectly fine. I think the problem is with your PC, environment, or your own code.

     

  • I've boiled it down to this using the defined classes above and partial HTTP response payload hardcoded as 'foo' below. I'm not hiding anything. This throws the exact same exception as if I were performing the HTTP request via HttpClient:

     try
    {
                        //This works
                        String fooNoShutdown = "{\"Items\":[{\"Timestamp\":\"2024-01-01T08:00:00Z\",\"Value\":111},{\"Timestamp\":\"2024-01-01T09:00:00Z\",\"Value\":222}]}";
                        Root<PiValue> fooClassWorking = JsonConvert.DeserializeObject<Root<PiValue>>(fooNoShutdown);
    
                        //This does not
                        String fooWithShutdown = "{\"Items\":[{\"Timestamp\":\"2024-01-01T08:00:00Z\",\"Value\":31.200000762939453},{\"Timestamp\":\"2024 - 01 - 25T03: 30:00Z\",\"Value\":{\"Name\":\"Shutdown\",\"Value\":254,\"IsSystem\":true}}]}";
                        Root<PiValue> fooClassNotWorking = JsonConvert.DeserializeObject<Root<PiValue>>(fooWithShutdown);
    }
    catch (Exception exxx)
    {
                        throw exxx;
    }

     

  • I used your fooWithShutdown string and it worked perfectly fine for me.

     

    You used PiValue for the deserialization but the class your shared earlier is called PiValue2.

    // PiValue class used but you only have shared a PiValue2 class
    Root<PiValue> fooClassNotWorking = JsonConvert.DeserializeObject<Root<PiValue>>

    What does the class definition for PiValue look like? OR what happens if you deserialized to Root<PIValue2> ?

     

    Again, the problem is not reproducible. Have you stepped through the debugger for the Value property's setter to trace down your issue?

     

     

  • PIValue and PIValue2 are the same class. One had Value as a string property, but I had since changed back after your comment that it should be an object type. I just tried using the PIValue2 and got the same exception.

    I have stepped through the debugger with "step into property" option turned on. The first json object steps into both Timestamp and Value setters, and works just fine. The second, the one with the 'Shutdown' object, steps into Timestamp but it throws the exception before stepping into the Value setter.

    A compiled version of this throws the same exception on a coworker's computer. I guess that doesn't rule out the environment since they're probably pretty similar.

     

     

     

  • Is there a filter expression I can append the the request URI to simply avoid these nested objects, or are there other nested objects I need to be concerned about?

  • Once again, I cannot reproduce your problem. Though I am using .NET Framework 4.7.2, I am also using Visual Studio 2022. Usually when I hear of someone on .NET Framework, I then to think there is an 80% change they are on older versions of Visual Studio.

     

    Things you may consider, if you get desparate enough to try new things:

     

    Upgrade to .NET Framework 4.8.

     

    Upgrade to Visual Studio 2022.

     

    I had NO problem with blanks in timestamp "2024 - 01 - 25T03: 30:00Z". However, you may consider trying to remove them, if possible.

     

    Try isolating out the deserializer call from your Fetch<T> method. Doing this may help you examine the bare bones string isolated away from anything HTTP or Task related. Something along the lines of:

     

    public class Deserializer
    {
        public static T ToInstance<T>(string jsonText)
        {
    
             // Here you can capture and view jsonText for any bad characters
             T instance = JsonConvert.DeserializeObject<T>(jsonText);
             // Here you can inspect instance in debugger.
            return instance;
        }
    }

     

     Finally, I would encourage you NOT to filter out Bad or SYSTEM states. There is some problem in your environment causing this - because again, I DO NOT experience this problem. Filtering out SYSTEM values may mask other issues.

     

    Instead, I would embrace the nested state object.

     

    public class DigitalState
    {
        public string Name { get; set; }
        public int Value { get; set; }
        public bool IsSystem { get; set; }
        public override ToString() => $"{Name} ({Value})";
    }

     

    Then inside your PiValue Value setter, instead of returning Double.NaN for anything bad, you could try to see if you can deserialize the value into a DIgitalState.

     

    Also, one way to check that Value is nested (as you call it) would be to try to convert it to be a JObject.

     

    var nestedJson = value as JObject;
    if (nestedJson == null)
    {
         // NOT nested JSON object.  Go thru regular checks.
         _Value = value; // or other checks
        return;
    }
    
    _Value = Deserializer.ToInstance<DigitalState>(nestedJson.ToString());

     

    Also, just a friendly reminder that as per IEEE specs, that a Double.NaN cannot be equal to another Double.NaN including itself. Not that you do any equality checks with the code that I have seen, but its always nice to keep this oddity in mind with working with NaN.

     

    I see you use a blocking Wait() call. You may consider decorating the method with async and eliminate the Wait() and subsequent Result call.

     

    // This is blocking
    responseTask.Wait();
    var result = responseTask.Result;
    
    // This is NOT blocking
    var result = await responseTask;

     

    The problem is that one must sprinkle the async decorator and implement await throughout their entire application, possibly include Main.

     

     

  • Rick, thanks for your help with this. Really appreciate it. I finally found a solution. You can decorate the Value property like this, and the exception stops being thrown. I can clearly see the nested json object being passed into the setter now too. The problem was that I was initializing the backing field like this:

     

    private object _Value = 0d;

     

     

    Also, thanks for the tip on DigitalState. I got that implemented and seems to be working.

    [JsonProperty("Value", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public object Value
            {
                get { return _Value; }
                set
                {
                    if (_Value != value)
                    {
                        double val = 0;
                        if (value is null)
                            _Value = Double.NaN;
                        else if (Double.TryParse(value.ToString(), out val))
                            _Value = val;
                        else //string, enumeration set, etc
                        {
                            var nestedJson = value as JObject;
                            if (nestedJson != null)
                            {
                                DigitalState ds = JsonConvert.DeserializeObject<DigitalState>(nestedJson.ToString());
                                _Value = ds;
                            }
                            else
                                _Value = Double.NaN;
                        }      
                    }
                }
            }

    Now, I kind of want to create explicit properties for NumericValue (double?), DigitalState, etc. One will contain a value, and the other(s) would be null. Curious if you have an opinion on that.

     

    As for NaN, I was using ' Double.IsNaN()' in my code to make the comparison.

  • I think adding convenient properties is a good idea. Since DigitalState is a class, it can be null. For NumericValue, you can either use a Nullable<double> OR return a Double.NaN instead of null. Depends upon whether you really need to distinguish when a value is NaN versus null, or if you want to consistently use null.