Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deterministic JSON serialization and deserialization support in Painless (should be deterministic) #37585

Closed
ypid-geberit opened this issue Jan 17, 2019 · 7 comments
Labels
:Core/Infra/Scripting Scripting abstractions, Painless, and Mustache Team:Core/Infra Meta label for core/infra team

Comments

@ypid-geberit
Copy link

ypid-geberit commented Jan 17, 2019

Describe the feature:

The Painless API reference does not contain any function that deals with JSON. Would it be possible to provide basic JSON functionally in Painless? If possible the serialization should be deterministic, see below.

I am including full ES document dumps in emails send by watches. Because the toJson Mustache function is not deterministic I needed to implement my own deterministic serialization function in Painless and realized that this is somewhat painful (e.g. not as it should be ;-) ).

This has been asked before: https://discuss.elastic.co/t/painless-scripting-json-functions/98511

For reference, this is my workaround. My code does not produce valid JSON and probably has bugs (not extensively tested). To work without regex support in Painless, I also put HTML indent support into this function.

/* JSON-like serialization Painless {{{
 * Ref: https://github.com/elastic/elasticsearch/issues/37585
 * License: Public domain (because the code is too simple).
 */
String get_indent_string(boolean html_output, int indent) {
  if (html_output) {
    return String.join("", Collections.nCopies(indent, "  "));
  } else {
    return String.join("", Collections.nCopies(indent, "  "));
  }
}
String serializ_leaf(def object) {
  if (object == null) {
    return "null";
  } else if (object instanceof String) {
    return '"' + object + '"';
  } else {
    return object.toString();
  }
}

ArrayList object_to_human_readable_string_lines(def object) {
  return object_to_human_readable_string_lines(object, false, 0, true);
}

ArrayList object_to_human_readable_string_lines(def object, boolean html_output) {
  return object_to_human_readable_string_lines(object, html_output, 0, true);
}

ArrayList object_to_human_readable_string_lines(def object, boolean html_output, int indent) {
  return object_to_human_readable_string_lines(object, html_output, indent, true);
}

ArrayList object_to_human_readable_string_lines(def object, boolean html_output, int indent, boolean indent_first_line) {
  ArrayList serialized_lines = new ArrayList();

  if (object instanceof Collection) {
    serialized_lines.add("[");
    indent += 1;
    for (def nested_object : object) {
      if (nested_object instanceof Collection || nested_object instanceof Map) {
        serialized_lines.addAll(object_to_human_readable_string_lines(nested_object, html_output, indent));
      } else {
        serialized_lines.add(get_indent_string(html_output, indent) + serializ_leaf(nested_object) + ",");
      }
    }
    indent -= 1;
    serialized_lines.add(get_indent_string(html_output, indent) + "]" + (indent == 0 ? "" : ","));
  } else if (object instanceof Map) {
    serialized_lines.add(get_indent_string(html_output, (indent_first_line ? indent : 0)) + "{");
    indent += 1;
    List keys = object.keySet().stream().sorted().collect(Collectors.toList());
    for (String key : keys) {
      def nested_object = object[key];
      if (nested_object instanceof Collection || nested_object instanceof Map) {
        ArrayList nested_serialized_lines = object_to_human_readable_string_lines(nested_object, html_output, indent, false);
        serialized_lines.add(get_indent_string(html_output, indent) + key + ": " + nested_serialized_lines.remove(0));
        serialized_lines.addAll(nested_serialized_lines);
      } else {
        serialized_lines.add(get_indent_string(html_output, indent) + key + ": " + serializ_leaf(nested_object) + ",");
      }
    }
    indent -= 1;
    serialized_lines.add(get_indent_string(html_output, indent) + "}" + (indent == 0 ? "" : ","));
  } else {
    serialized_lines.add(serializ_leaf(object) + ",");
  }

  return serialized_lines;
}
/* }}} */
@Tim-Brooks Tim-Brooks added the :Core/Infra/Scripting Scripting abstractions, Painless, and Mustache label Jan 18, 2019
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra

@honzakral
Copy link
Contributor

honzakral commented Apr 15, 2020

👍 on json support in painless. My ideal interface would be a pair of functions for (de)serializaing json objects. Example usage (naming lifter from python, should be adapted to painless' naming convention):

Strng json = '{"obj": {"l": [1,2, 3]}}';
HashMap o = loads(json);
o.obj.l.add(4);
// return {"obj":{"l":[1,2,3,4]}}
return dumps(o);

Ideally this would also support dumps(o, true) which would prettify the output, same as dumps(o, indent=4, sort_keys=True) in Python.

Supported types for dumps should also include all Date related types which would be serialized into the ISO format.

@rjernst rjernst added the Team:Core/Infra Meta label for core/infra team label May 4, 2020
@honzakral
Copy link
Contributor

PR sent in #60925

@ypid-geberit ypid-geberit changed the title JSON serialization and deserialization support in Painless Deterministic JSON serialization and deserialization support in Painless (should be deterministic) Oct 7, 2020
@ypid-geberit
Copy link
Author

ypid-geberit commented Oct 7, 2020

#63278 has been merged which is based on #60925, thanks! That is a great step forward 👍

It does not yet ensure deterministic serialization so I would like to keep this issue open. See discussion in #60925 (comment)

Cc: @stu-elastic

@stu-elastic
Copy link
Contributor

Agreed this issue should be kept open, there is need for deterministic serialization.

@rjernst rjernst added the needs:triage Requires assignment of a team area label label Dec 3, 2020
@jdconrad jdconrad removed the needs:triage Requires assignment of a team area label label Dec 9, 2020
@rjernst
Copy link
Member

rjernst commented May 25, 2024

This has been open for quite a while, and we haven't made much progress on this due to focus in other areas. For now I'm going to close this as something we aren't planning on implementing. We can re-open it later if needed.

@rjernst rjernst closed this as completed May 25, 2024
@ypid
Copy link

ypid commented May 26, 2024

@rjernst Can you please set the correct issue status then? "Deterministic" is in the title but is not solved. This could otherwise give then wrong impression when people search for issues.

2024-05-26_16-27

@rjernst rjernst closed this as not planned Won't fix, can't repro, duplicate, stale May 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Core/Infra/Scripting Scripting abstractions, Painless, and Mustache Team:Core/Infra Meta label for core/infra team
Projects
None yet
Development

No branches or pull requests

8 participants