bblanchon / ArduinoJson

📟 JSON library for Arduino and embedded C++. Simple and efficient.
https://arduinojson.org
MIT License
6.7k stars 1.12k forks source link

[Question]: How to just check nested JSON for double values in a key? #1172

Closed effelle closed 4 years ago

effelle commented 4 years ago

@bblanchon thank you so much for your library! I'm trying to update part of the code to support nested values on a firmware for ESP8266. Normally I need to decode normal JSON pairs, like

{"ENERGY":{"Total":2.684,"Yesterday":0.020,"Today":0.006,"Period":0.04,"Power":0.49,
"ApparentPower":4.71,"ReactivePower":4.70,"Factor":0.10,"Frequency":50.04,"Voltage":226.3,
"Current":0.021}}

The problem begin with two values per key:

{"ENERGY":{
"Total":3.262,"Yesterday":0.000,"Today":0.005,"Period":1.34,"Power":[23.63,67.17],
"ApparentPower":[23.63,67.17],"ReactivePower":[0.00,0.00],"Factor":[1.00,1.00],
"Voltage":229.3,"Current":[0.103,0.293]}}

I use the json to create single entities for Home Assistant, and is giving issue because the firmware doesn't create entities listening to [0] and [1]. What I didn't understand is how to check if there are two values (I don't need to parse the result, just look at it) and consequently pass to another void the correct flag to generate the entities where just sensor (example: ENERGY) and subsensor (example: Power) are needed. Here's the relevant part of my code:

StaticJsonBuffer<500> jsonBuffer;      
      JsonObject& root = jsonBuffer.parseObject(sensordata);
      if (!root.success()) {
        AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: jsonBuffer failed to parse '%s'"), sensordata);
        continue;
      }
      for (auto sensor : root) {
        const char* sensorname = sensor.key;
        JsonObject& sensors = sensor.value.as<JsonObject>();
        if (!sensors.success()) {
          AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: JsonObject failed to parse '%s'"), sensordata);
          continue;
        }
        for (auto subsensor : sensors) {
          HAssAnnounceSensor(sensorname, subsensor.key);
        }
      }
    }
    yield();
  } while (hass_xsns_index != 0);
}

I think I need add some sort of "checkpoint" before for (auto sensor : root). What approach would you suggest to solve my issue? Thanks, Federico

bblanchon commented 4 years ago

Hi @effelle,

If you want to know the type of a value, use JsonVariant::is():

JsonVariant power = root["Power"];
if (power.is<double>()) {
  // just one value
}
if (power.is<JsonArray>()) {
  // several values
  for (JsonVariant v : power.as<JsonArray>()) {
    // ...
  } 
}

Best Regards, Benoit

effelle commented 4 years ago

Benoît, thanks for your reply. I've added the JsonVariant and I think I got it. The only issue I have is when a value is null for some reasons (happens from time to time with DHT11). In that specific case the bool dpower will be set as True. Is a rare case, but could happens. Anyway, for the sake of knowledge here the complete void:

void HAssAnnounceSensors(void)
{
  uint8_t hass_xsns_index = 0;

  do {
    mqtt_data[0] = '\0';
    int tele_period_save = tele_period;
    tele_period = 2;                                  
    XsnsNextCall(FUNC_JSON_APPEND, hass_xsns_index); 
    tele_period = tele_period_save;

    char sensordata[512];                            
    strlcpy(sensordata, mqtt_data, sizeof(sensordata));

    if (strlen(sensordata)) {
      sensordata[0] = '{';                             
      snprintf_P(sensordata, sizeof(sensordata), PSTR("%s}"), sensordata);  
      AddLog_P2(LOG_LEVEL_INFO, PSTR("JSON: Input list '%s'"), sensordata);

      StaticJsonBuffer<500> jsonBuffer;      
      JsonObject& root = jsonBuffer.parseObject(sensordata);
      if (!root.success()) {
        AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: jsonBuffer failed to parse '%s'"), sensordata);
        continue;
      }
      for (auto sensor : root) {
        const char* sensorname = sensor.key;
        JsonObject& sensors = sensor.value.as<JsonObject>();
        if (!sensors.success()) {
          AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: JsonObject failed to parse '%s'"), sensordata);
          continue;
        }
        for (auto subsensor : sensors) {
          bool dpower= false;
          const char* subsensorname = subsensor.key;
          AddLog_P2(LOG_LEVEL_INFO, PSTR("NAME: sensorname '%s'"), subsensorname);
          JsonVariant multival = sensors[subsensorname];
          if (!multival.is<double>()) {
            dpower = true;
          }
          AddLog_P2(LOG_LEVEL_INFO, PSTR("CHKP: dpower value %d"), dpower);

          HAssAnnounceSensor(sensorname, subsensor.key);
        }
      }
    }
    yield();
  } while (hass_xsns_index != 0);
}

I tested it with some json and I got fairly good results:

19:09:27 JSON: Input list '{"ANALOG":{"A0":609}}'
19:09:27 NAME: sensorname 'A0'
19:09:27 CHKP: dpower value 0
19:09:27 JSON: Input list '{"BME280":{"Temperature":28.1,"Humidity":40.5,
"Pressure":1003.4}}'19:09:27 
19:09:27 NAME: sensorname 'Temperature'
19:09:27 CHKP: dpower value 0
19:09:28 NAME: sensorname 'Humidity'
19:09:28 CHKP: dpower value 0
19:09:28 NAME: sensorname 'Pressure'
19:09:28 CHKP: dpower value 0
20:32:34 JSON: Input list '{"ENERGY":{"Total":2.684,"Power":[23.63,67.17],
"ApparentPower":[23.63,67.17],"Voltage":229.3,"Current":[0.103,0.293]}}'
20:32:34 NAME: sensorname 'Total'
20:32:34 CHKP: dpower value 0
20:32:35 NAME: sensorname 'Power'
20:32:35 CHKP: dpower value 1
20:32:35 NAME: sensorname 'ApparentPower'
20:32:35 CHKP: dpower value 1
20:32:35 NAME: sensorname 'Voltage'
20:32:35 CHKP: dpower value 0
20:32:35 NAME: sensorname 'Current'
20:32:35 CHKP: dpower value 1

Thanks for your time!

Federico

bblanchon commented 4 years ago

You're welcome Federico. Thank you for using ArduinoJson.

effelle commented 4 years ago

Sorry to bother you again but do I have a way to count the number of values in the array? Seems I need to manage also values like

{"ENERGY":{"Total":2.684,"Power":[23.63,67.17,44.18]}}

Should return Total =1 Power = 3 Seems I need to use JsonArray::size() or better JsonObject::size().

bblanchon commented 4 years ago

JsonArray::size() is the right answer: it returns the number of elements in the array.

effelle commented 4 years ago

HI Benoît,

I've used JsonArray::size() to count the number of items:

if (subsensor.value.is<JsonArray&>()) {  
  JsonArray& subsensors = subsensor.value.as<JsonArray&>();
  subqty = subsensors.size();

But I realized it takes almost 90 bytes. Is there a way to archive the same result using less flash space? Thanks,

Federico

bblanchon commented 4 years ago

Hi @effelle,

JsonArray::size() does a linear scan to count the elements. In most cases, you can avoid this function by calling begin()/end().

Best regards, Benoit

effelle commented 4 years ago

I think I'm misunderstanding the use of begin()/end() because that function use the same space of size() in flash. Just to be sure we are on the same page, let's say subsensor.value is "[22,34,1023.4]". My new code should be:

          if (subsensor.value.is<JsonArray&>()) {
            JsonArray& subsensors = subsensor.value.as<JsonArray&>();
            for (auto value : subsensors) {
            subqty++;
            }

Am I right to say is virtually the same but with more lines of code?

Edit: I think my problem is more about the jsonbuffer I have on the beginning of the void:

      StaticJsonBuffer<500> jsonBuffer;

I'm starting to think subsensor is crating a second 500 buffer because if I remove the second jsonarray I'm trying to fix it takes 0.2k less on flash space.