jsonmodify.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (C) 2019 The Android Open Source Project
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import argparse
  17. import collections
  18. import json
  19. import sys
  20. def follow_path(obj, path):
  21. cur = obj
  22. last_key = None
  23. for key in path.split('.'):
  24. if last_key:
  25. if last_key not in cur:
  26. return None,None
  27. cur = cur[last_key]
  28. last_key = key
  29. if last_key not in cur:
  30. return None,None
  31. return cur, last_key
  32. def ensure_path(obj, path):
  33. cur = obj
  34. last_key = None
  35. for key in path.split('.'):
  36. if last_key:
  37. if last_key not in cur:
  38. cur[last_key] = dict()
  39. cur = cur[last_key]
  40. last_key = key
  41. return cur, last_key
  42. class SetValue(str):
  43. def apply(self, obj, val):
  44. cur, key = ensure_path(obj, self)
  45. cur[key] = val
  46. class Replace(str):
  47. def apply(self, obj, val):
  48. cur, key = follow_path(obj, self)
  49. if cur:
  50. cur[key] = val
  51. class ReplaceIfEqual(str):
  52. def apply(self, obj, old_val, new_val):
  53. cur, key = follow_path(obj, self)
  54. if cur and cur[key] == int(old_val):
  55. cur[key] = new_val
  56. class Remove(str):
  57. def apply(self, obj):
  58. cur, key = follow_path(obj, self)
  59. if cur:
  60. del cur[key]
  61. class AppendList(str):
  62. def apply(self, obj, *args):
  63. cur, key = ensure_path(obj, self)
  64. if key not in cur:
  65. cur[key] = list()
  66. if not isinstance(cur[key], list):
  67. raise ValueError(self + " should be a array.")
  68. cur[key].extend(args)
  69. # A JSONDecoder that supports line comments start with //
  70. class JSONWithCommentsDecoder(json.JSONDecoder):
  71. def __init__(self, **kw):
  72. super().__init__(**kw)
  73. def decode(self, s: str):
  74. s = '\n'.join(l for l in s.split('\n') if not l.lstrip(' ').startswith('//'))
  75. return super().decode(s)
  76. def main():
  77. parser = argparse.ArgumentParser()
  78. parser.add_argument('-o', '--out',
  79. help='write result to a file. If omitted, print to stdout',
  80. metavar='output',
  81. action='store')
  82. parser.add_argument('input', nargs='?', help='JSON file')
  83. parser.add_argument("-v", "--value", type=SetValue,
  84. help='set value of the key specified by path. If path doesn\'t exist, creates new one.',
  85. metavar=('path', 'value'),
  86. nargs=2, dest='patch', default=[], action='append')
  87. parser.add_argument("-s", "--replace", type=Replace,
  88. help='replace value of the key specified by path. If path doesn\'t exist, no op.',
  89. metavar=('path', 'value'),
  90. nargs=2, dest='patch', action='append')
  91. parser.add_argument("-se", "--replace-if-equal", type=ReplaceIfEqual,
  92. help='replace value of the key specified by path to new_value if it\'s equal to old_value.' +
  93. 'If path doesn\'t exist or the value is not equal to old_value, no op.',
  94. metavar=('path', 'old_value', 'new_value'),
  95. nargs=3, dest='patch', action='append')
  96. parser.add_argument("-r", "--remove", type=Remove,
  97. help='remove the key specified by path. If path doesn\'t exist, no op.',
  98. metavar='path',
  99. nargs=1, dest='patch', action='append')
  100. parser.add_argument("-a", "--append_list", type=AppendList,
  101. help='append values to the list specified by path. If path doesn\'t exist, creates new list for it.',
  102. metavar=('path', 'value'),
  103. nargs='+', dest='patch', default=[], action='append')
  104. args = parser.parse_args()
  105. if args.input:
  106. with open(args.input) as f:
  107. obj = json.load(f, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder)
  108. else:
  109. obj = json.load(sys.stdin, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder)
  110. for p in args.patch:
  111. p[0].apply(obj, *p[1:])
  112. if args.out:
  113. with open(args.out, "w") as f:
  114. json.dump(obj, f, indent=2, separators=(',', ': '))
  115. f.write('\n')
  116. else:
  117. print(json.dumps(obj, indent=2, separators=(',', ': ')))
  118. if __name__ == '__main__':
  119. main()