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.

     

  • 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:

     

     

     

     

Reply
  • 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:

     

     

     

     

Children
No Data