Skip to content

Support Nutriment Nutrient Units #871

Open
@davidpryor

Description

@davidpryor

Why - Problem description

The openfoodfacts server supports passing of nutriment units through the following parameter "nutriement__unit". The current version of the SDK does not support passing this parameter to the server. Currently, with this restriction, a user must read through the SDK code and/or server code to find what units the backend is expected nutrients to be passed as.

What - Proposed solution

Expand the current Nutriments.setValue method signature to accept an optional (for backwards compatibility), parameter called "unit" of type Unit?. If a unit is passed, save these in the object state similar to how nutriment values are saved. Otherwise, fall back to the currently existing "typicalUnit" attribute on the Nutrient Enum. This fallback isn't technically needed, but may make it easier to for users reading through the code. An alternate/addition would be to update the Nutrient Enum docstrings with their default unit.

The easiest way to add it in, would be to make another internal map state in the Nutriment class called "_unitMap".

class Nutriments extends JsonObject {
  ...
  final Map<String, String> _unitMap = <String, String>{};
  ...
  /// Returns the map key for that [nutrient] unit.
  String _getUnitTag(final Nutrient nutrient) => '${nutrient.offTag}_unit';
  ...
    Nutriments setValue(
    final Nutrient nutrient,
    final PerSize perSize,
    final double? value, {
    final Unit? unit = null,
  }) {
    final Unit finalUnit = unit ?? nutrient.typicalUnit;
    _map[_getTag(nutrient, perSize)] = value;
    _unitMap[_getUnitTag(nutrient)] = finalUnit.offValue;
    return this;
  }
  @override
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> result = <String, dynamic>{};
    for (final Nutrient nutrient in Nutrient.values) {
      for (final PerSize perSize in PerSize.values) {
        final String tag = _getTag(nutrient, perSize);
        final double? value = _map[tag];
        if (value != null) {
          result[tag] = value;
        } else if (_map.containsKey(tag)) {
          result[tag] = '';
        }
        final String? unit = _unitMap[_getUnitTag(nutrient)];
        if (unit != null) {
          result[_getUnitTag(nutrient)] = unit;
        }
      }
    }
    return result;
  }

}

Also, implementing similar enum patterns for the Unit enum, one could move the OFF string representation for each unit onto the enum itself and simplify the serialization.

- enum Unit { KCAL, KJ, G, MILLI_G, MICRO_G, MILLI_L, L, PERCENT, UNKNOWN }
+ enum Unit {
+   KCAL(offValue: 'kcal'),
+   KJ(offValue: 'kj'),
+   G(offValue: 'g'),
+   MILLI_G(offValue: 'mg'),
+   MICRO_G(offValue: 'mcg'),
+   MILLI_L(offValue: 'ml'),
+   L(offValue: 'liter'),
+   PERCENT(offValue: 'percent'),
+   UNKNOWN(offValue: '');
+ 
+   final String offValue;
+   const Unit({required String this.offValue});
+ }

https://github.com/davidpryor/openfoodfacts-dart/tree/expose-nutrient-unit-parameter

Alternatives you've considered

  1. Require users to determine the default unit required for the off-server and convert their internal representation units to the expected unit. This still needs documentation updates to let the user know what units are needed. Also, it more makes sense to expose the server-api through the client.
  2. Same as the solution above but with a large refactor. One moves responsibilities of serializing an individual nutrient/unit/modifier/etc combo into a new class.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    To do

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions