Repository: flutter
Issue: Unstructured JSON API
Labels: c: new feature,framework,dependency: dart,P3,team-framework,triaged-framework

A while ago while noodling around I wrote the following class. I'm posting it here for us to consider if we want to expose this or something like it in the framework.

```dart
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@proxy
class Json {
  factory Json(dynamic input) {
    if (input is Json)
      return wrap(input._value);
    return wrap(input);
  }

  factory Json.list(List<dynamic> input) {
    return new Json.raw(input.map<Json>(wrap).toList());
  }

  // (This differs from "real" JSON in that we don't allow duplicate keys.)
  factory Json.map(Map<dynamic, dynamic> input) {
    final Map<String, Json> values = <String, Json>{};
    input.forEach((dynamic key, dynamic value) {
      key = key.toString();
      assert(!values.containsKey(key), 'Json.map keys must be unique strings');
      values[key] = wrap(value);
    });
    return new Json.raw(values);
  }

  const Json.raw(this._value);

  final dynamic _value;

  static Json wrap(dynamic value) {
    if (value == null) {
      return const Json.raw(null);
    } else if (value is num) {
      return new Json.raw(value.toDouble());
    } else if (value is List) {
      return new Json.list(value);
    } else if (value is Map) {
      return new Json.map(value);
    } else if (value == true) {
      return const Json.raw(true);
    } else if (value == false) {
      return const Json.raw(false);
    } else if (value is Json) {
      return value;
    }
    return new Json.raw(value.toString());
  }

  dynamic unwrap() {
    if (_value is Map) {
      final Map<String, dynamic> values = <String, dynamic>{};
      _value.forEach((String key, Json value) {
        values[key] = value.unwrap();
      });
      return values;
    } else if (_value is List) {
      return _value.map<dynamic>((Json value) => value.unwrap()).toList();
    } else {
      return _value;
    }
  }

  double toDouble() => _value as double;
  bool toBoolean() => _value as bool;
  @override
  String toString() => _value.toString();
  Type get valueType => _value.runtimeType;

  String toJson() {
    // insert JSON serializer here
    throw new Exception('not implemented');
  }

  dynamic operator [](dynamic key) {
    return _value[key];
  }

  void operator []=(dynamic key, dynamic value) {
    _value[key] = wrap(value);
  }

  @override
  dynamic noSuchMethod(Invocation invocation) {
    if (invocation.isGetter)
      return this[_symbolName(invocation.memberName)];
    if (invocation.isSetter)
      return this[_symbolName(invocation.memberName)] = invocation.positionalArguments[0];
    return super.noSuchMethod(invocation);
  }

  // Workaround for https://github.com/dart-lang/sdk/issues/28372
  String _symbolName(Symbol symbol) {
    // WARNING: Assumes a fixed format for Symbol.toString which is *not*
    // guaranteed anywhere.
    final String s = '$symbol';
    return s.substring(8, s.length - 2);
  }

  bool operator <(dynamic other) {
    if (other.runtimeType != Json)
      return _value < other;
    return _value < other._value;
  }

  bool operator <=(dynamic other) {
    if (other.runtimeType != Json)
      return _value <= other;
    return _value <= other._value;
  }

  bool operator >(dynamic other) {
    if (other.runtimeType != Json)
      return _value > other;
    return _value > other._value;
  }

  bool operator >=(dynamic other) {
    if (other.runtimeType != Json)
      return _value >= other;
    return _value >= other._value;
  }

  dynamic operator -(dynamic other) {
    if (other.runtimeType != Json)
      return _value - other;
    return _value - other._value;
  }

  dynamic operator +(dynamic other) {
    if (other.runtimeType != Json)
      return _value + other;
    return _value + other._value;
  }

  dynamic operator /(dynamic other) {
    if (other.runtimeType != Json)
      return _value / other;
    return _value / other._value;
  }

  dynamic operator ~/(dynamic other) {
    if (other.runtimeType != Json)
      return _value ~/ other;
    return _value ~/ other._value;
  }

  dynamic operator *(dynamic other) {
    if (other.runtimeType != Json)
      return _value * other;
    return _value * other._value;
  }

  dynamic operator %(dynamic other) {
    if (other.runtimeType != Json)
      return _value % other;
    return _value % other._value;
  }

  dynamic operator |(dynamic other) {
    if (other.runtimeType != Json)
      return _value | other;
    return _value | other._value;
  }

  dynamic operator ^(dynamic other) {
    if (other.runtimeType != Json)
      return _value ^ other;
    return _value ^ other._value;
  }

  dynamic operator &(dynamic other) {
    if (other.runtimeType != Json)
      return _value & other;
    return _value & other._value;
  }

  dynamic operator <<(dynamic other) {
    if (other.runtimeType != Json)
      return _value << other;
    return _value << other._value;
  }

  dynamic operator >>(dynamic other) {
    if (other.runtimeType != Json)
      return _value >> other;
    return _value >> other._value;
  }

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != Json)
      return _value == other;
    return _value == other._value;
  }

  @override
  int get hashCode => _value.hashCode;
}


void main() {
  final Json value = new Json({
    'foo': {
      'bar': [ 0, 1, 'two', 3 ],
    },
    'baz': true,
    'quux': true,
  });

  assert(value.foo.bar[2] == 'two');
  assert(value.baz == true);
  assert(value.baz == value.quux);

  value.foo.bar[0] = 'zero';
  assert(value.foo.bar[0].valueType == String);

  assert(value.foo.bar[1].toDouble() == 1.0);
  assert(value.foo.bar[1] < 2.0);
  assert(value.foo.bar[1] < value.foo.bar[3]);
}
```